面向对象编程(Object Oriented Programming,缩写为 OOP)是目前主流的编程范式。
它将真实世界各种复杂的关系,抽象为一个个对象,然后由对象之间的分工与合作,完成对真实世界的模拟。
典型的面向对象编程语言(比如 C++ 和 Java),都有“类”(class)这个概念。
所谓“类”就是对象的模板,对象就是“类”的实例。
但是,JavaScript 语言的对象体系,不是基于“类”的,而是基于构造函数(constructor)和原型链(prototype)。
对象
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);
就是专门用来生成实例对象的函数。它就是对象的模板,描述实例对象的基本结构。
构造函数就是一个普通的函数,但具有自己的特征和用法。所以不用new
也可以执行
为了与普通函数区别,构造函数名字的第一个字母通常大写。
特点:
this
关键字,代表了所要生成的对象实例。new
命令。ES5 的写法,toString()
方法是可枚举的
Object.keys(Point.prototype); // ["toString"]
Object.getOwnPropertyNames(Point.prototype); // ["constructor","toString"]
new
命令:执行构造函数,返回一个实例对象。
new
命令时,根据需要,构造函数也可以接受参数。new
命令,直接调用构造函数,构造函数就变成了普通函数,并不会生成实例对象。而且由于后面会说到的原因,this
这时代表全局对象,将造成一些意想不到的结果。new
命令一起使用,use strict
。这样的话,一旦忘了使用new
命令,直接调用构造函数就会报错。new
命令,如果发现没有使用,则直接返回一个实例对象。new
命令原理
prototype
属性。this
关键字。this
指的是一个新生成的空对象,所有针对this
的操作,都会发生在这个空对象上。this
对象),将其“构造”为需要的样子。return
语句,而且return
后面跟着一个对象,new
命令会返回return
语句指定的对象;return
语句,返回this
对象。this
关键字的函数)使用new
命令,则会返回一个空对象。new
命令总是返回一个对象,要么是实例对象,要么是return
语句指定的对象。若return
语句返回的是字符串,new
命令就会忽略该语句。new.target
:函数内部可以使用new.target
属性。如果当前函数是new
命令调用,new.target
指向当前函数,否则为undefined
。new
命令。Object.create()
方法。因为有时拿不到构造函数,只能拿到一个现有的对象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
new
命令。prototype
属性上面,所以类的新方法可加在prototype
对象上面,Object.assign()
一次向类添加多个方法。Object.assign(Point.prototype, {toString() {},toValue() {},
});
Object.keys(Point.prototype); // []
Object.getOwnPropertyNames(Point.prototype); // ["constructor","toString"]
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'
让一个构造函数继承另一个构造函数
// 定义构造函数 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`方法。
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()
?
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
上一篇:力扣64.最小路径和