什么是面向对象编程
对比面向过程编程
面向过程:关注的重点是动词,是分析出解决问题需要的步骤,编写对应的函数来实现每个步骤,依次来调用函数。
面向对象:关注的终点是主谓,把问题分解成各个对象,每个对象表示在具体步骤内的各种行为。
面向对象的三大特性:
1.封装:让使用对象的人不考虑内部实现,只考虑功能使用,把内部的代码保护起来,只留出一些api供使用方使用。
2.继承:为了代码复用,从父类上继承出一些方法和属性,子类也有自己的一些属性。
3.多态:是不同对象作用同一操作产生的不同结果,多态的思想实际上是把”想做什么“和”谁去做“分开。
什么时候适合使用面向对象的思想
1.在比较复杂的问题面前,或者说参与方较多的时候,面向对象可以很好的简化问题,能够更好的扩展和维护。
2.在比较简单的问题面前,也可以一步步的按照步骤调用,用面向过程。
创建对象的几种方式
1.普通方式
很不灵活,如果再创建一个黑棋选手,则需要重新写
const Player = new Object();
Player.color = 'white';
Player.start = function() {
console.log('white下棋');
}
2.工厂模式
或者工厂模式,这两种方式都无法识别对象类型,比如 Player 的类型只是 Object
function createObject(color) {
const Player = new Object();
Player.color = color;
Player.start = function() {
console.log(this.color+'下棋')
}
return Player;
}
3.构造函数
缺点:this挂的属性/对象,都是指向当前对象的,所以在实例化的时候,通过this添加的属性或者方法,就会在内存中 创建一份。
function Player(color) {
this.color = color;
this.start = function() {
console.log(this.color+'下棋');
}
}
4.原形
优点:start方法只会在内存中存一份
function Player(color) {
this.color = color;
}
Player.prototype.start = function() {
console.log(this.color+'下棋');
}
const white = new Player('white');
const black = new Player('black');
white.start();
black.start();
5.静态属性
绑定 构造函数上的属性方法,需要通过构造函数访问。
function Player(color) {
this.color = color;
if(!Player.count){
Player.count = 0;
}
Player.count++;
}
手写一个new方法
new的本质是:
1.创建一个新的空的object对象p1
2.将构造函数的原型对象赋值给新的p1的proto
3.将this指向新的p1对象
4.有return的情况
1.如果显性的return为一个非object类型,则返回p1对象
2.如果显性的return为一个object类型,则返回当前object对象
function createNewObject() {
let p1 = {};
const playerConstructor = [].shift.call(arguments); // 弹出第一个Player的参数
p1.__proto__ = playerConstructor.prototype;
const resultObj = playerConstructor.apply(p1,arguments);
return typeof resultObj !== 'object' ? p1 : resultObj;
}
什么是原型链
当某个对象访问对象内部的某个属性时,如果找到了这个属性,就返回这个属性的值,如果找不到就去访问它的构造函数的原型对象的属性值,如果找到了这个属性就返回这个属性的值,如果找不到就访问Object的原型对象,如果找到了这个属性就返回这个属性的值,否则返回undefined。
Object.prototype.name = 'Object';
function Player() {}
Player.prototype.name = 'Player';
const p1 = new Player();
p1.name = 'p1';
console.log(p1.name); //p1
// 删除p1.name
delete p1.name;
console.log(p1.name); //Player
delete Player.prototype.name;
console.log(p1.name); //Object
继承
1.原型链继承
function Parent(){}
function Child(){}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
缺点:
如果有属性是引用类型的,一旦某个实例修改了这个属性,所有实例都会受到影响
无法传参
2.构造函数继承
function Parent(name) {
this.name = name;
this.start = function(){
console.log(this.name+'start');
}
}
function child(id,name){
const args = Array.from(arguments).slice(1)
Parent.apply(this,args);
this.id = id;
}
缺点:
每一次调用构造函数,都会再创建一遍方法,占用内存
3.组合继承
function Parent(name) {
this.name = name;
}
Parent.prototype.start = function(){
console.log(this.name+'start');
}
function child(id,name){
const args = Array.from(arguments).slice(1)
Parent.apply(this,args);
this.id = id;
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
缺点:
Parent.appy(this,args)和new Parent()
调用了两边构造函数。
4.寄生继承
function Parent(name) {
this.name = name;
}
Parent.prototype.start = function(){
console.log(this.name+'start');
}
function child(id,name){
const args = Array.from(arguments).slice(1)
Parent.apply(this,args);
this.id = id;
}
//方式1
let TempFunction = function(){};
TempFunction.prototype = Parent.prototype;
child.prototype = TempFunction.prototype;
//方式2
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
上述无法直接用Child.prototype = Parent.prototype
,会存在原型对象污染问题
5.class
class Parent{
constructor(name){
this.name = name;
}
start(){
console.log(this.name+'start');
}
}
class Child extends Parent{
constructor(id,name){
super(name);
this.id = id;
}
}