面向对象编程&原型&原型链

568 阅读4分钟

一、面向对象编程

 1、什么是面向对象编程

面向对象是一种编程思想,经常被拿来和面向过程比较

1.1 面向过程编程 VS 面向对象编程

1、面向过程编程:
面向过程关注的重点是动词,
是分析出解决问题需要的步骤,
然后编写函数实现每个步骤,
最后依次调用函数

2、面向对象编程:
面向对象关注重点是主谓,
是把构成问题的事物拆解成各个对象,
而拆解出对象的目的也不是为了实现某个步骤,
而是为了描述这个事物在当前问题中的各种行为


1.2 面向对象特点


1、继承
就是为了实现代码的复用,从父类上继承出一些方法属性,子类也有自己的一些属性

2、封装
让使用对象的人不考虑内部实现,只考虑功能使用,把内部的代码保护起来,
只留一些api供用户使用

3、多态
是不同对象作用于同一操作产生不同的效果。
多态的思想实际上是把“想做什么”和“谁去做”分开

2、JS中的面向对象

2.1 对象包含什么

属性
方法

2.2 常用内置对象

  1. Object
  2. Array
  3. Date
  4. Function
  5. RegExp

2.3 创建对象


1、普通方法
通过new Object()构造函数创建对象,然后再设置属性和方法

new Object()

let Player= new Object();
// 再添加属性和方法给
objectPlayer.name = 'Tom';
工厂模式

function createObject(){
    const Player = new Object();
    Player.name='Tom';
    Player.run = function(){
        console.log(this.name+"run")
    }
    return Player;
}
2、构造函数/实例

this添加的属性和方法总是指向当前对象,所以实例化的时候,
通过this添加的属性和方法都会在内存中赋值一份,这样会造成内存浪费

缺点:
这样会造成内存浪费

好处:
即使改变某一个对象的属性和方法,不会影响到其他的对象

function Player(name){
    this.name = name;
    this.run = function(){
      console.log(this.name+"run")
    }
}
const p1 = new Player("p1");
const p2 = new Player("p2");

3、原型

通过原型继承的方法不是自身的,我们要在原型链上一层层的查找,
这样创建的好处是指在内存中创建一次,实例化的对象都会指向这个prototype对象

function Player(name){
    this.name = name;
}
Player.prototype.run = function(){
    console.log(name+"run")
}
const p1=  new Player("p1");
const p2=  new Player("p2");

4、静态属性

是绑定在构造函数上的属性方法,需要通过构造函数访问

function Player(name){
     if(!Player.total){
         Player.total = 0;
     }
      Player.total++;
}

二、原型及原型链

1、 new

1、创建一个继承自Player.prototype的新对象
2、新对象的属性__proto__指向Player.prototype
3、将this指向新建的对象
4、返回新对象
  • 4.1如果构造函数没有显式返回值,则返回this
  • 4.2如果构造函数有显式返回值,是基本类型,那么返回this
  • 4.3如果过构造函数有显式返回值,是对象,则返回这个对象

function objectFactory(){
    let obj = new Object(); 
   let Constructor = [].shift.call(arguments);
    obj.__proto__ = Constructor.prototype;
    let ret = Constructor.apply(obj,arguments);
    return typeof ret === 'object'?ret:obj
}

2、 原型链

当读取实例的属性时,如果找不到,就会查找与对象相关联的原型中的属性,
如果查找不到,就是去找原型的原型,一直找到最顶层为止


三、继承

1、原型链继承

1.1 实现

function Parent(){
    this.name = "parent";
}
Parent.prototype.getName = function(){
    console.log(this.name)
}
function Child(){}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var child = new Child();
child.getName();// parent

1.2 缺点


1、如果有属性是引用类型的,一旦某个实例修改了这个属性,
所有的实例都会受到影响

2、创建Child实例的时候,不能传参

2、构造函数继承

2.1 实现

function Parent(name, actions) {
    this.name = name;
    this.actions = actions;}function Child(id, name, actions) {
    Parent.apply(this, Array.from(arguments).slice(1))
   this.id = id;
}
const child1 = new Child(1, "c1", ["eat"]);
const child2 = new Child(2, "c2", ["sing", "jump", "rap"]);
console.log(child1.name); // c1
console.log(child2.name); // c2

2.2 缺点

1、属性或者方法想被继承的话,只能在构造函数中定义。
如果方法在构造函数内定义了,那么每次创建实例都会创建一遍方法,多占一块内存

3、组合继承

3.1 实现

function Parent(name,actions){
    this.name = name;
    this.actions = actions;
}
Parent.prototype.eat = function(){
    console.log(this.name+"-eat")}function Child(id){
    Parent.apply(this,Array.from(arguments).slice(1))
    this.id = id;
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
const child1 = new Child(1, "c1", ["eat"]);
const child2 = new Child(2, "c2", ["run"]);
child1.eat(); // c1 - eat
child2.eat(); // c2 - eat
console.log(child1.eat === child2.eat); // true

3.2 缺点

1、调用了两次构造函数,组了重复的操作

Parent.apply(this,Array.from(arguments).slice(1))
Child.prototype = new Parent();

4、寄生组合继承

4.1 实现

function Parent(name,action){
    this.name = name;
    this.actions = actions;
}
Parent.prototype.eat = function(){
    console.log(this.name+"-eat")
}
function Child(id){
    Parent.apply(this,Array.from(arguments).slice(1));
    this.id  =id;
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
const child1 = new Child(1, "c1", ["eat"]);
const child2 = new Child(2, "c2", ["run"]);