彻底理解构造函数和类

ES5构造函数和ES6类

构造函数和类都是用于创建对象的机制

  • ES5 之前JavaScript 并没有 class 类关键字,是使用function来声明一个构造函数(类)的,之后通过new关键字来对其进行调用来实现类似于类的功能

    • 在其他面向的编程语言里面,构造函数是存在于类中的一个方法,称之为构造方法
    • ES5 之前JavaScript中的构造函数有点不太一样,构造函数扮演了其他语言中类的角色
  • ES6 之后JavaScript 可以像别的语言一样,通过class来声明一个类,这是一种语法糖,类实际上是基于ES5实现的一种高级语法

1. 构造函数

  • 构造函数也称为构造器(constructor),通常是我们在创建对象时会调用的函数
  • 构造函数也是一个普通的函数,命名以大写字母开头,从表现形式来说,和普通的函数没有任何区别
  • 但如果这个函数被new操作符来调用了,那么这个函数就称之为是一个构造函数

实例

实例 是由类或构造函数创建的具体对象,通过 new 关键字调用构造函数创建,或通过 class 关键字定义类并创建

function Person(name, age, height) {
  this.name = name;
  this.age = age;
  this.height = height;
}
var p1 = new Person("ablice", 20, 188);

p1就是通过 new Person 创建的实例

new操作符

具体学习这篇文章:https://juejin.cn/post/7397399723601215488

实例方法

实例方法是定义在类或构造函数的原型对象(prototype)上的方法

// 定义一个构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
}

// 定义实例方法
Person.prototype.greet = function() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};

// 创建实例
const person1 = new Person('Alice', 30);
const person2 = new Person('Bob', 25);

// 调用实例方法
person1.greet(); // 输出: Hello, my name is Alice and I am 30 years old.
person2.greet(); // 输出: Hello, my name is Bob and I am 25 years old.

类方法/静态方法

类方法也称为静态方法,它是定义在类或构造函数本身上的方法,通过类或构造函数调用,不能通过实例调用

// 定义一个构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;
}

// 定义实例方法
Person.prototype.greet = function() {
  console.log(`Hello, ${this.name}, ${this.age}`);
};

// 定义类方法
Person.jumping = function(){
  console.log(this.name + '在jumping')
}

// 创建实例
const person1 = new Person('Alice', 30);
const person2 = new Person('Bob', 25);

// 调用类方法
Person.jumping(); // 输出: Person在jumping
// 调用实例方法
person1.greet(); // 输出: Hello, Alice, 30
person2.greet(); // 输出: Hello, Bob, 25

原型

构造函数是函数自然有一个 prototype 属性,该属性是一个对象,包含由该构造函数创建的所有实例共享的属性和方法
具体理解看这篇文章:https://blog.csdn.net/qq_45730399/article/details/141104727?spm=1001.2014.3001.5501

实现继承

具体学习这篇文章:https://juejin.cn/post/7399986979735781391

2. ES6类

在 ES6中,引入了 class 关键字,使得面向对象编程变得更加直观和易于理解。但 JavaScript 依旧是基于原型的语言,class 只是语法糖,它提供了一种更简洁、更清晰的方式来定义使用构造函数和原型继承

定义

按照构造函数形式创建类,不仅仅和编写普通的函数过于相似,而且代码并不容易理解

  • 在ES6(ECMAScript2015)新的标准中使用了 class关键字来直接定义类
  • 本质是构造函数、原型链的语法糖而已
  • 类的原型和原型中的constructor指向和构造函数都是相同的
  • 唯一不同是类不能像构造函数一样直接调用
  • 可以使用两种方式来声明类:typeof Person === function
    • 类声明:class Person {}`
    • 类表达式:var Student = class {}

constructor

如果我们希望在创建对象的时候给类传递一些参数,这个时候应该如何做呢?

  • 每个类都可以有一个自己的构造函数(方法),这个方法的名称为固定的constructor
  • 每个类只能有一个constructor,如果包含多个构造函数,那么会抛出异常
  • 当我们通过new一个类的时候会调用这个类的constructor,这时new做的事情和new一个构造函数做的事情是一样的

实例方法

// 定义一个类
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  // 定义实例方法
  greet() {
      console.log(`Hello, ${this.name}, ${this.age}`);
  }
}

// 创建实例
const person1 = new Person('Alice', 30);
const person2 = new Person('Bob', 25);

// 调用实例方法
person1.greet(); // 输出: Hello, Alice, 30
person2.greet(); // 输出: Hello, Bob, 25

类方法/静态方法

类方法也称为静态方法,它是定义在类或构造函数本身上的方法,通过类或构造函数调用,用于定义直接使用类来执行的方法,不需要有类的实例,使用static关键字来定义

// 定义一个类
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  // 定义实例方法
  greet() {
      console.log(`Hello, ${this.name}, ${this.age}`);
  }
  
  // 定义类方法
  static jumping = function(){
    console.log(this.name + '在jumping')
  }
}

// 创建实例
const person1 = new Person('Alice', 30);
const person2 = new Person('Bob', 25);
// 调用类方法
person1.jumping(); // 输出: Person在jumping
// 调用实例方法
person1.greet(); // 输出: Hello, Alice, 30
person2.greet(); // 输出: Hello, Bob, 25

访问器方法

对象可以添加settergetter函数的,那么类也是可以的

class Person {
  constructor(name) {
    this._name = name;
  }
  set name(newName) {
    console.log("调用了name的setter方法");
    this._name = newName;
  }
  get name() {
    console.log("调用了name的getter方法");
    return this._name;
  }
}

var p1 = new Person("alice");
console.log(p1.name); // 调用了name的getter方法, alice

// 设置新的名字,会调用setter方法
p1.name = "bob"; // 调用了name的setter方法

// 再次获取名字,会调用getter方法
console.log(p1.name); // 调用了name的getter方法, bob

extends

ES6中新增了使用extends关键字,可以帮助我们实现继承

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  running() {
    console.log(this.name + "在running");
  }
  static jumping() {
    console.log(this.name + "在jumping");
  }
}
class Student extends Person {}
var s1 = new Student("bob", 20);
console.log(s1); // {name: 'bob', age: 20}
s1.running(); // bob在running

继承内置类

我们可以让自己的类继承内置类,比如Array,然后对Array进行扩展

// es5继承并扩展Array
Array.prototype.lastItem = function () {
  return this[this.length - 1];
};
var a1 = [1, 2, 3];
// var a2 = new Array(1, 2, 3);
console.log(a1.lastItem()); // 3

// es6继承并扩展Array
class myArray extends Array {
  lastItem() {
    return this[this.length - 1];
  }
}
var a2 = new myArray(10, 20, 30);
console.log(a2.lastItem()); // 30

super关键字

  • 在子(派生)类的构造函数中,必须在使用 this 之前调用 super,以确保父类的构造函数被正确调用并初始化父类的属性
  • 在子(派生)类的方法中,可以使用 super.methodName 调用父类的方法
  • super的使用位置有三个:子类的构造函数、实例方法、静态方法
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  running() {
    console.log(this.name + "在running");
  }
  static jumping() {
    console.log(this.name + "在jumping");
  }
}

class Student extends Person {
  constructor(name, age, school, score) {
    // 1. 在constructor中,子类想扩展自己的属性时,需要在扩展之前使用super
    super(name, age);
    this.school = school;
    this.score = score;
  }
  running() {
    // 2. 想在实例方法中写自己的逻辑,也想使用父类时
    console.log(`考上${this.school}`); // 考上清华了
    super.running(); // bob在running
  }
  static jumping() {
    // 2. 想在类方法中写自己的逻辑,也想使用父类时
    console.log(`哇考了${this.score}`); // 哇考了undefined分
    super.jumping(); // Student在jumping
  }
}
var s1 = new Student("bob", 20, "清华", 750);
console.log(s1); // {name: 'bob', age: 20, school: '清华', score: 750}
s1.running(); 
Student.jumping();

混入mixin

JavaScript的类只支持单继承只能有一个父类,
在开发中需要在一个类中添加继承多个类时可以使用混入:

function mixin(...classes) {
  if (classes.length === 0) {
      throw new Error("At least one class should be provided");
  }
    
  return classes.reduce((CombinedClass, CurrentClass) => {
    return class extends CombinedClass {
      constructor(...args) {
        super(...args);
        Object.assign(this, new CurrentClass(...args));
      }
    };
  });
}

// 测试代码
class CanEat {
  eat() {
    console.log('Eating');
  }
}

class CanWalk {
  walk() {
    console.log('Walking');
  }
}

class CanSwim {
  swim() {
    console.log('Swimming');
  }
}

const MixedClass = mixin(CanEat, CanWalk, CanSwim);

const obj = new MixedClass();
obj.eat(); // 输出: Eating
obj.walk(); // 输出: Walking
obj.swim(); // 输出: Swimming

babelES6类转ES5源码

在转换的源码中可以看到ES6的类和继承的原理还是构造函数和原型,只是它们的语法糖

class代码的转换

class代码转换前:

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  running() {
    console.log(this.name + "在running");
  }
  static jumping() {
    console.log(this.name + "在jumping");
  }
}
var p1 = new Person('bob', 18)

转换后:

function _typeof(o) {
  "@babel/helpers - typeof";
  return (
    (_typeof =
      "function" == typeof Symbol && "symbol" == typeof Symbol.iterator
        ? function (o) {
            return typeof o;
          }
        : function (o) {
            return o &&
              "function" == typeof Symbol &&
              o.constructor === Symbol &&
              o !== Symbol.prototype
              ? "symbol"
              : typeof o;
          }),
    _typeof(o)
  );
}
function _classCallCheck(a, n) {
  // 如果a不是n的实例就抛错说class类不能直接调用
  if (!(a instanceof n))
    throw new TypeError("Cannot call a class as a function");
}
function _defineProperties(e, r) {
  for (var t = 0; t < r.length; t++) {
    var o = r[t];
    (o.enumerable = o.enumerable || !1),
      (o.configurable = !0),
      "value" in o && (o.writable = !0),
      Object.defineProperty(e, _toPropertyKey(o.key), o);
  }
}
function _createClass(e, r, t) {
  // e: 构造函数Person,r: 实例方法数组,t:静态方法数组
  return (
    r && _defineProperties(e.prototype, r), // 将实例方法定义Person原型上
    t && _defineProperties(e, t), // 将静态方法定义Person上
    Object.defineProperty(e, "prototype", { writable: !1 }), // prototype设为不可写
    e
  );
}
function _toPropertyKey(t) {
  var i = _toPrimitive(t, "string");
  return "symbol" == _typeof(i) ? i : i + "";
}
function _toPrimitive(t, r) {
  if ("object" != _typeof(t) || !t) return t;
  var e = t[Symbol.toPrimitive];
  if (void 0 !== e) {
    var i = e.call(t, r || "default");
    if ("object" != _typeof(i)) return i;
    throw new TypeError("@@toPrimitive must return a primitive value.");
  }
  return ("string" === r ? String : Number)(t);
}

// 纯函数
var Person = /*#__PURE__*/ (function () {
  function Person(name, age) {
    _classCallCheck(this, Person); // 判断this是不是Person的实例
    this.name = name;
    this.age = age;
  }
  return _createClass(
    Person,
    [
      {
        key: "running",
        value: function running() {
          console.log(this.name + "在running");
        }
      }
    ],
    [
      {
        key: "jumping",
        value: function jumping() {
          console.log(this.name + "在jumping");
        }
      }
    ]
  );
})();
var p1 = new Person("bob", 18);
  • 29
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值