【JavaScript】 面向对象编程

面向对象编程是主流编程范式,JavaScript基于构造函数和原型链实现对象继承。构造函数用于生成实例对象,new命令执行构造函数并返回实例。ES6引入Class,提供更传统的类语法,但本质仍基于原型。类的方法默认不可枚举,且必须与new一起使用。继承可通过构造函数或Class的extends关键字实现,子类必须在构造函数中调用super()。
摘要由CSDN通过智能技术生成

1 面向对象编程

  • 面向对象编程(Object Oriented Programming,缩写为 OOP)是目前主流的编程范式。

  • 它将真实世界各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。

  • 典型的面向对象编程语言(比如 C++ 和 Java),都有“类”(class)这个概念。

  • 所谓“类”就是对象的模板,对象就是“类”的实例。

  • 但是,JavaScript 语言的对象体系,不是基于“类”的,而是基于构造函数(constructor)和原型链(prototype)。

  • 对象

    • 对象是单个实物的抽象
    • 对象是一个容器,封装了属性(property)和方法(method)

1.1 生成实例对象——构造函数

JavaScript 语言中,生成实例对象的传统方法是通过构造函数。

// 定义构造函数 Point,该函数内有属性 x 和 y
function Point(x, y) {
  this.x = x;
  this.y = y;
}

// Object.prototype.toString():返回当前对象对应的字符串形式。(Point 函数 也是一种对象)
Point.prototype.toString = function () {
  return "(" + this.x + ", " + this.y + ")";
};

// 执行构造函数 Point(1, 2),返回一个实例对象 p, 内有属性 x = 1 和 y = 2
var p = new Point(1, 2);

在这里插入图片描述

1.1.1 构造函数

  • 就是专门用来生成实例对象的函数。它就是对象的模板,描述实例对象的基本结构。

  • 构造函数就是一个普通的函数,但具有自己的特征和用法。所以不用new也可以执行

  • 为了与普通函数区别,构造函数名字的第一个字母通常大写。

  • 特点:

    • 函数体内部使用了this关键字,代表了所要生成的对象实例。
    • 生成对象的时候,必须使用new命令。
  • ES5 的写法,toString()方法是可枚举的

Object.keys(Point.prototype); // ["toString"]
Object.getOwnPropertyNames(Point.prototype); // ["constructor","toString"]

1.1.2 new

  • new命令:执行构造函数,返回一个实例对象。

    • 使用new命令时,根据需要,构造函数也可以接受参数。
    • 如果忘了使用new命令,直接调用构造函数,构造函数就变成了普通函数,并不会生成实例对象。而且由于后面会说到的原因,this这时代表全局对象,将造成一些意想不到的结果。
    • 为了保证构造函数必须与new命令一起使用,
    • 构造函数内部使用严格模式,即第一行加上use strict。这样的话,一旦忘了使用new命令,直接调用构造函数就会报错。
    • 构造函数内部判断是否使用new命令,如果发现没有使用,则直接返回一个实例对象。
  • new命令原理

    1. 创建一个空对象,作为将要返回的对象实例。
    2. 将这个空对象的原型,指向构造函数的prototype属性。
    3. 将这个空对象赋值给函数内部的this关键字。
    4. 开始执行构造函数内部的代码。

1.1.3 this

  • 构造函数内部,this指的是一个新生成的空对象,所有针对this的操作,都会发生在这个空对象上。
  • 构造函数之所以叫“构造函数”,就是说这个函数的目的,就是操作一个空对象(即this对象),将其“构造”为需要的样子。

1.1.4 返回

  • 如果构造函数内部有return语句,而且return后面跟着一个对象,new命令会返回return语句指定的对象;
  • 否则,就会不管return语句,返回this对象。
  • 另一方面,如果对普通函数(内部没有this关键字的函数)使用new命令,则会返回一个空对象。
  • 因为new命令总是返回一个对象,要么是实例对象,要么是return语句指定的对象。若return语句返回的是字符串,new命令就会忽略该语句。

1.1.5 属性

  • new.target:函数内部可以使用new.target属性。如果当前函数是new命令调用,new.target指向当前函数,否则为undefined
  • 使用这个属性,可以判断函数调用的时候,是否使用new命令。

1.1.6 现有的对象作为模板,生成新的实例对象

  • 现有的对象作为模板,生成新的实例对象,使用Object.create()方法。因为有时拿不到构造函数,只能拿到一个现有的对象

1.2 生成实例对象——Class 类

ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。

class Point {
  // 构造方法
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return "(" + this.x + ", " + this.y + ")";
  }
}

var p = new Point(1, 2);

typeof Point; // "function"
Point === Point.prototype.constructor; // true

在这里插入图片描述

1.2.1 类

  • 生成对象的时候,必须使用new命令。
  • 类的数据类型就是函数,类本身就指向构造函数。
  • 类的所有方法都定义在类的prototype属性上面,所以类的新方法可加在prototype对象上面,Object.assign()一次向类添加多个方法。
Object.assign(Point.prototype, {
  toString() {},
  toValue() {},
});
  • 类的内部所有定义的方法,都是不可枚举的(non-enumerable),这与 ES5 不同
Object.keys(Point.prototype); // []
Object.getOwnPropertyNames(Point.prototype); // ["constructor","toString"]

1.2.2 方法

  • constructor()
    • 类的默认方法,通过new命令生成对象实例时,自动调用该方法。
    • 一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加。
    • constructor()方法默认返回实例对象(即this),完全可以指定返回另外一个对象。
class Foo {
  constructor() {
    return Object.create(null);
  }
}

new Foo() instanceof Foo; // false
  • 类必须使用new调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new也可以执行。
class Foo {
  constructor() {...}
}

Foo() // TypeError: Class constructor Foo cannot be invoked without 'new'

1.3 对象的继承——构造函数

让一个构造函数继承另一个构造函数

  1. 子类继承父类的实例:在子类的构造函数中,调用父类的构造函数
  2. 子类继承父类的原型:让子类的原型指向父类的原型
// 定义构造函数 Shape
function Shape() {
  this.x = 0;
  this.y = 0;
}

// 定义方法 move
Shape.prototype.move = function (x, y) {
  this.x += x;
  this.y += y;
  console.info("Shape moved.");
};

Rectangle构造函数继承Shape

// 第一步,子类继承父类的实例
function Rectangle() {
  // 调用父类构造函数
  Shape.call(this); // 子类是整体继承父类。
}

// 另一种写法
function Rectangle() {
  this.base = Shape;
  this.base();
}

// 第二步,子类继承父类的原型
// 现有的对象作为模板,生成新的实例对象,使用`Object.create()`方法。因为有时拿不到构造函数,只能拿到一个现有的对象
// 子类的原型要赋值为`Object.create(Shape.prototype)`,而不是直接等于`Shape.prototype`。
// 否则后面一行对`Rectangle.prototype`的操作,会连父类的原型`Shape.prototype`一起修改掉。
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;

// 另外一种写法,是 Rectangle.prototype 等于一个父类实例。
// 这种写法也有继承的效果,但是子类会具有父类实例的方法。有时可能不是我们需要的,所以不推荐使用这种写法
Rectangle.prototype = new Shape();

采用这样的写法以后,instanceof运算符会对子类和父类的构造函数,都返回true

var rect = new Rectangle();

rect instanceof Rectangle; // true
rect instanceof Shape; // true

有时只需要单个方法的继承

ClassB.prototype.print = function () {
  ClassA.prototype.print.call(this);
  // some code
};
// 上面代码中,子类`B`的`print`方法先调用父类`A`的`print`方法,再部署自己的代码。这就等于继承了父类`A`的`print`方法。

1.4 对象的继承——class 类

Class 可以通过extends关键字实现继承,让子类继承父类的属性和方法。

class Point {
  /* ... */
}

// 子类 ColorPoint 继承 Point 类的所有属性和方法
class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); // 调用父类的constructor(x, y)
    this.color = color;
  }

  toString() {
    return this.color + " " + super.toString(); // 调用父类的toString()
  }
}
  • super关键字表示父类的构造函数,用来新建一个父类的实例对象。

  • ES6 规定,子类必须在constructor()方法中调用super(),否则就会报错。

    • 这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,添加子类自己的实例属性和方法。
    • 如果不调用super()方法,子类就得不到自己的this对象。
  • 为什么子类的构造函数,一定要调用super()?

    • 原因就在于 ES6 的继承机制,与 ES5 完全不同。
    • ES5 的继承机制,是先创造一个独立的子类的实例对象,然后再将父类的方法添加到这个对象上面,即“实例在前,继承在后”。
    • ES6 的继承机制,则是先将父类的属性和方法,加到一个空的对象上面,然后再将该对象作为子类的实例,即“继承在前,实例在后”。
    • 这就是为什么 ES6 的继承必须先调用super()方法,因为这一步会生成一个继承父类的this对象,没有这一步就无法继承父类。
  • 注意,这意味着新建子类实例时,父类的构造函数必定会先运行一次。

class Foo {
  constructor() {
    console.log(1);
  }
}

class Bar extends Foo {
  constructor() {
    super();
    console.log(2);
  }
}

const bar = new Bar();
// 1
// 2
// 子类构造函数调用`super()`时,会执行一次父类构造函数
  • 在子类的构造函数中,只有调用super()之后,才可以使用this关键字,否则会报错。

    • 这是因为子类实例的构建,必须先完成父类的继承,只有super()方法才能让子类实例继承父类。
  • 如果子类没有定义constructor()方法,这个方法会默认添加,并且里面会调用super()

  • 也就是说,不管有没有显式定义,任何一个子类都有constructor()方法。

class ColorPoint extends Point {}

// 等同于
class ColorPoint extends Point {
  constructor(...args) {
    super(...args);
  }
}
// 实例对象`cp`同时是`ColorPoint`和`Point`两个类的实例,这与 ES5 的行为完全一致。
let cp = new ColorPoint(25, 8, "green");

cp instanceof ColorPoint; // true
cp instanceof Point; // true
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值