什么是面向对象
- 面向对象是一种编程思想,JS 本身就是基于面向对象构建出来的(例如:JS 中有很多内置类,Promise 就是 ES6 中新增的一个内置类,我们可以基于 new Promise 来创建一个实例来管理异步编程,在开发中经常会使用到 Promise),平时开发用到的 Vue/React 也是基于面向对象构建出来的,他们都是类,使用时都是创建出他们的实例来操作的
- JS 中的面向对象,和其他编程语言有不一样的地方,JS 中类和实例是基于原型和原型链机制来处理的。JS 中关于类的重载、重写、继承也和其他语言不太一样
特性
- 封装:低耦合高内聚
- 多态:重载和重写
- 重载:方法名相同,形参个数或者类型不一样,当调用时,根据传参的不同走不同的方法(JS 有变量提升,同名函数会覆盖,所以 JS 不存在真正意义的重载,JS 的重载指的是同一个方法,根据传参不同实现不同的逻辑)
- 重写:在类的继承中,子类可以重写父类中的方法
- 继承:子类继承父类中的属性和方法,目的是让子类的实例能够调用父类中的属性和方法
JS 继承的方案
- 原型继承
- 原理:利用原型和原型链的机制,让父类中的属性和方法在子类实例的原型链上
- 特点:
- 不像其他语言的继承(其他语言的继承一般是拷贝继承,子类继承父类,会把父类中的属性和方法拷贝一份到子类中,供其子类的实例调用),JS 是把父类的原型放在子类实例的原型链上,实例想调用这些方法,是基于
__proto__原型链查找机制完成的 - 子类可以重写父类上的方法(这样会导致父类的其他实例也受到影响)
- 父类中私有或共有的属性方法,最后都会变为子类的公有的属性和方法
- 不像其他语言的继承(其他语言的继承一般是拷贝继承,子类继承父类,会把父类中的属性和方法拷贝一份到子类中,供其子类的实例调用),JS 是把父类的原型放在子类实例的原型链上,实例想调用这些方法,是基于
function A(x) {
this.x = x;
}
A.prototype.getX = function () {
console.log(this.x);
};
function B(y) {
this.y = y;
}
B.prototype = new A(200);
B.prototype.getY = function () {
console.log(this.y);
};
// 保证原型重定向后的完整性
B.prototype.constructor = B;
let b1 = new B(100);
b1.getY();
b1.getX();
-
call 继承
- 原理:在子类的将父类当作普通函数执行,通过 call 改变 this 指向,让父类中的 this 指向子类的实例,相当于给子类的实例设置私有的属性和方法
- 特点:
- 只能继承父类私有的属性和方法(原型链上的共有属性方法无法继承)
- 父类私有的变为子类私有的
function A(x) {
this.x = x;
}
A.prototype.getX = function () {
console.log(this.x);
};
function B(y) {
// this是B的实例b1
A.call(this, 200); // b1.x = 200
this.y = y;
}
B.prototype.getY = function () {
console.log(this.y);
};
let b1 = new B(100);
console.info(b1.x);
-
组合继承
- 原理:利用 call 和原型继承,改变原型指向实现
- 特点:
- 父类的私有属性方法变为子类的私有属性和方法,父类的公有属性方法变为子类的公有属性方法,但是调用了两次父类
- 父类的私有属性方法既是子类的私有属性方法,也是子类的公有属性方法
- 子类可以重写父类上的方法(这样会导致父类的其他实例也受到影响)
function A(x) {
this.x = x;
}
A.prototype.getX = function () {
console.log(this.x);
};
function B(y) {
// this是B的实例b1
A.call(this, 200); // b1.x = 200
this.y = y;
}
B.prototype = new A(200);
B.prototype.constructor = B;
B.prototype.getY = function () {
console.log(this.y);
};
let b1 = new B(100);
b1.getX();
-
寄生组合继承
-
原理:利用 call 和类似原型继承的方式,改变原型指向实现(通过 Object.create 创建空对象的方式只继承父类的公有属性和方法)
-
特点:
- 父类的私有属性方法变为子类的私有属性和方法,父类的公有属性方法变为子类的公有属性方法,且不用调用两次父类(推荐使用的方法)
- 子类可以重写父类上的方法(这样会导致父类的其他实例也受到影响)
-
扩展:重写 Object.create
// 重写Object.create Object.create = function (obj) { // IE浏览器不允许操作__proto__ // let oo = {}; // oo.__proto__ = obj; // return oo; function Fn() {} fn.prototype = obj; return new Fn(); };
-
function A(x) {
this.x = x;
}
A.prototype.getX = function () {
console.log(this.x);
};
function B(y) {
A.call(this, 200);
this.y = y;
}
// Object.create(obj) 创建一个空对象,让空对象的__proto__指向obj
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
B.prototype.getY = function () {
console.log(this.y);
};
let b1 = new B(100);
console.info(b1);
-
ES6 中的继承
- 特点:
- ES6 中基于
class关键词创造出来的类不能当作普通函数执行 - call 继承就不能使用了,会报错
- 不允许重定向原型的指向,原型继承不能使用
- 组合继承和寄生组合继承都不能使用
- ES6 中增加公有属性要在 prototype 上加
A.prototype.name = "张三"; - ES6 中使用 extends 和 super 来实现继承),原理还是寄生组合继承的方式,只是语法不一样
extends => B.prototype.__proto__ = A.prototypesuper(200) => A.call(this, 200) 把父类当作普通函数执行,把this指向变为子类的实例
class A { constructor(x) { this.x = x; } getX() { console.info(this.x); } } // ES6中增加公有属性要通过prototype来加 A.prototype.name = "张三"; class B extends A { constructor(y) { super(200); this.y = y; } getY() { console.info(this.y); } } let b1 = new B(100); console.info(b1); - 子类只要继承父类,可以不写 constructor,一旦写了,则 constructor 的第一句必须是 super(),否则会报错
// 不写 constructor,默认会创建constructor constructor(...args) { super(...args) }
- ES6 中基于
- 特点: