面向对象编程
什么是面向过程编程
面向过程重点是分析出解决问题的步骤,然后编写函数实现每个步骤,最后依次调用函数。
什么是面向对象编程
面向对象编程重点是把构成问题的事物拆解成一个个对象,拆解出对象的目的不是为了解决问题,而是为了描述事物在当前各种问题中的行为。
面向对象编程的特点和优点
特点:
- 封装性:让使用对象的人不考虑内部实现,只考虑功能的使用,把内部的代码保护起来,只留出一些API供使用方使用,
- 继承性:为了代码复用性,可以继承父类的一些属性和方法,子类也可以有自己的一些方法。
- 多态性:是不同对象作用于同一操作产生不同的结果,多态的思想是把“想做什么”和“谁去做”分开。
优点:
在解决复杂问题有多方参与的情况下,面向对象编程的优势就体现出来了,能够更好的简化问题,更好的维护和扩展,
创建对象的方式
1、普通模式
let o = new Object();
复制代码
2、工厂模式
function createPerson(name, age, job){
const o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayname = function() {
console.log(this.name);
};
return o;
}
let person = createPerson('vivi', 25, 'Web developer');
复制代码
缺点: 无法识别出创建的对象是什么类型。
3、构造函数模式
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayname = function(){
console.log(this.name);
};
}
let person = new Person('vivi', 25, 'Web developer');
复制代码
优点:改变某个对象的属性和方法不会影响其他对象,可以确保实力被标识为特定的类型。
缺点:this挂的属性/方法,都是指向当前对象的,所以在实例化的时候,都会在内存中复制一份,这就造成了一定程度上的内存浪费。
4、原型模式
function Person(){}
Person.prototype.name = 'vivi';
Person.prototype.age = '18';
Person.prototype.sayname = function(){
console.log(this.name);
};
let person = new Person();
复制代码
优点: 原型上的属性、方法只创建一次,所有的实例共享一份属性、方法。
原型、原型链
牢记:每个实例对象都有一个私有属性__proto__,都会指向它的构造函数的对象属性Prototype,而每个prototype也有自己的原型对象__proto__,层层向上,直到一个对象的原型对象为null,就代表找到顶端了,一条原型链就形成了。
怎么找到原型对象
通过对象的__proto__属性可以找到原型对象。通过构造函数new一个实例,实例的__proto__属性指向了构造函数的prototype, 构造函数的prototype.constructor指向构造函数。
手写一个new的实现过程
new的实现步骤:
1、新建一个对象
2、将对象的__proto__属性指向构造函数的Prototype。
3、执行构造函数,并将this指向新建的对象。
4、如果构造函数的返回值是对象,则返回该返回值对象。
5、返回新建的这个对象。
function newInstance(Fn, ...args) {
const obj = {};
obj.__proto__ = Fn.prototype;
let result = Fn.call(obj, ...args);
return result instanceof Object ? result : obj;
}
复制代码
new返回不同的类型会有哪些表现?
- 1、如果构造函数没有显示返回值,则返回this。
function Person(name, age) {
this.name = name;
this.age = age;
}
const p1 = newInstance(Person, 'vivi', 18);// Person {name: "vivi", age: 18}
复制代码
- 2、如果构造函数有显示返回值,是基本数据类型,比如String、Number、Boolean等,那么还是返回this。
function Person(name, age) {
this.name = name;
this.age = age;
return name;
}
const p2 = newInstance(Person, 'vivi', 18);// Person {name: "vivi", age: 18}
复制代码
- 3、如果构造函数有显示返回值,是对象类型,那么返回该对象。
function Person(name, age) {
this.name = name;
this.age = age;
// 显示返回值为对象
return {
name
}
}
const p3 = newInstance(Person, 'vivi', 18); // {name: "vivi"}
复制代码
继承
原型链继承
function Parent() {
this.name = "Father";
}
Parent.prototype.getName = function(){
console.log(this.name);
}
function Child() {}
Child.prototype = new Parent();
const child1 = new Child();
child1.getName();
复制代码
隐含的问题:
1、一旦有引用类型的属性,某个实例更改了这个属性,那么所有的实例都会受影响。
2、实例无法传参
构造函数继承
function Parent(name, actions) {
this.name = name;
this.actions = actions;
}
function Child(id) {
Parent.apply(this, Array.prototype.slice.call(arguments, 1));
this.id = id;
}
const c1 = new Child(1, 'coco', ['eat', 'sleep']);
复制代码
隐含问题:
1、属性或者方法如果被继承的话,只能在构造函数内被定义,如果方法在构造函数内定义了,那么每次创建实例都会创建一遍方法,多占用一块内存。
组合式继承
原型链继承实现了基本的继承,但是方法存在Prototype上,子类可以直接调用,但是引用类型的属性会被所有实例共享,并且不能传参。
构造函数继承解决了原型链继承的两个问题,但是构造函数内的方法会被重复创建,导致内存占用过多。
funtion Parent(name, actions) {
this.name = name;
this.actions = actions;
}
Parent.prototype.eat = function() {
console.log(`${this.name} -- eat`);
}
function Child(id, name, actions) {
this.id = id;
Parent.apply(this, Array.from(arguments).slice(1));
}
Child.prototype = new Parent();
Child.prototype.constructor = child;
const c1 = new Child(1, 'Chris', ['hhhh']);
复制代码
隐含问题:
调用了两次父的构造函数,做了无意义的操作。
寄生组合式继承
funtion Parent(name, actions) {
this.name = name;
this.actions = actions;
}
Parent.prototype.eat = function() {
console.log(`${this.name} -- eat`);
}
function Child(id, name, actions) {
this.id = id;
Parent.apply(this, Array.from(arguments).slice(1));
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = child;
const c1 = new Child(1, 'Chris', ['hhhh']);
复制代码
Class继承
class Parent {
constructor() {
this.name = 'Vivi';
}
getName() {
console.log('getName');
}
}
class Child extends Parent {
constructor() {
super();
}
}
const c1 = new Child();
c1.getName(); // getName
复制代码