【稍微聊聊】面向对象编程OOP

写在前面:

Javascript是一种基于对象的语言,但是在对于class的态度上其实一直表现的不冷不热,直到ES6才引入了class概念,而class概念就是把"属性"(property)和"方法"(method),封装成一个对象。

一, 生成实例对象的原始模式

如果我们要以对象的形式,声明一个猫,应该怎么做?


let Cat = {
    name: "",
    color: ""
}

然后,我们根据这个原型对象的规格,生成两个对象实例:

let cat1 = {}

cat1.name = "阿珍"

cat1.color = "白色"

let cat2 = {}

cat2.name = "阿强"

cat2.color = "黄色"

这样我们就创建好了, 但是问题是 我们创建的实例跟原型没有任何关系,而且如果实例多了,就很麻烦。


二, 原始模式的改进

为了解决这么麻烦的问题。。我们直接写个函数啊,是不是傻!

    function Cat(name,color) {

    return {

      name:name,

      color:color

    }

  }
  
  let cat1 = Cat("阿珍", "白色")
  
  let cat2 = Cat("阿强", "黄色")

这样解决了创建麻烦的问题,但是依然有问题。作为同一个对象原型的实例,阿珍和阿强没有任何关系。。


3, 构造模式

为了解决这个问题,js引入了构造函数。

构造函数引入了this,对构造函数用new运算符,就可以生成实例,并且this的的变量会绑定在实例对象上

    function Cat(name,color) {
        this.name = name
        this.color = color
  }
  
  let cat1 = new Cat("阿珍", "白色")
  
  let cat2 = new Cat("阿强", "黄色")

构造函数的实例有一个constructor属性,指向他们的构造函数。同时构造函数还有一个方法来判断实例和原型的关系

    cat1.constructor        // Cat
    
    cat1 instanceof Cat     // true 

这样看起来是解决了之前提到的所有问题 但是新问题又来了。内存的优化问题。

如果我们在Cat中添加新的方法和属性

    function Cat(name,color) {
        this.name = name
        this.color = color
        this.type = "猫科动物"
        this.eat = ()=>{
            alert("吃个毛")
        }
  }
  
  alert(cat1.type); // 猫科动物

  cat1.eat(); // 吃个毛

由于在原型中心添加的方法是固定的写法,所以所有新的实例都有一模一样的属性,这样的话就会导致内占用更多的内存。

那么这么来想,既然是一样的东西,有没有办法让他们指向内存的同一块区域呢? 就是说不管我调用cat1.type还是调用cat2.type都是指向同一个变量呢?

console.log(cat1.eat == cat2.eat)       // false

四, Prototype

Javascript规定,每个构造函数都有一个prototype属性,这个属性是一个对象,而这个对象会被这个构造函数的所有实例继承。

这就意味着,我们可以把那些不会改变的,或者默认的属性写在prototype上,然后再生成实例

    function Cat(name,color) {
        this.name = name
        this.color = color
  }
  
  Cat.prototype.type = "猫科动物"
  
  Cat.prototypr.eat = () => {
      alert ("吃个毛")
  }
  
  alert(cat1.type); // 猫科动物

  cat1.eat(); // 吃个毛
  
  console.log(cat1.eat == cat2.eat)       // true

五, Prototype的辅助方法

1. isPrototypeOf()

用于某个prototype和实例之间的关系

  alert(Cat.prototype.isPrototypeOf(cat1)); //true

  alert(Cat.prototype.isPrototypeOf(cat2)); //true

然而并不知道该怎么用。。。。

2. hasOwnProperty()

每个实例对象都有一个hasOwnProperty()方法,用来判断某一个属性到底是本地属性,还是继承自prototype对象的属性。

console.log(cat1.hasOwnProperty("name"), cat1.hasOwnProperty("type"))


//  true false
3. in运算符

in运算符可以用来判断,某个实例是否含有某个属性,不管是不是本地属性。

(也可以用来遍历属性)

  alert("name" in cat1); // true

  alert("type" in cat1); // true

6, 构造函数的继承

1 使用call/apply

这种方法最简单

    function Animal() {
        this.species = "动物"
    }
    
    function Cat(name, coloe) {
        Animal.apply(this)
        
        this.name = name
        
        this.color = color
    }
2 使用prototype

把Cat的prototype指向一个animal实例,就可以继承Animal所有的属性和方法了

但是要注意prototype中的constructor指向

    function Animal() {
        this.species = "动物"
    }
    
    function Cat(name, coloe) {
        this.name = name
        
        this.color = color
    }

    Cat.prototype = new Animal();
    
    Cat.prototype.constructor = Cat;
    
    Cat.prototype.type = "猫科动物"
    
    Cat.prototype.eat = ()=>{
        alert("吃个毛")
    }

这里注意一步

Cat.prototypr.constructor = Cat

因为我们用的这个方法,Cat.prototype的constructor变成了Animal,这样会导致原型链紊乱(cat1明明是用Cat构造的),所以需要手动校正。

一定要注意,我们每次这样操作的时候都要注意原型链是否正常

3 是用prototype直接继承

之前说过,一般的不变的量都是写在prototype里的,所以我们直接让被继承的把值赋给要继承的构造函数的prototype

    function Animal() {
    
    }
    
    Animal.prototype.species = "动物"
    
    function Cat(name, coloe) {
        this.name = name
        
        this.color = color
    }

    Cat.prototype = Animal.prototype;
    
    Cat.prototype.type = "猫科动物"
    
    Cat.prototype.eat = ()=>{
        alert("吃个毛")
    }

这个的优势就很大了,但是问题在于,现在Cat.prototype和 Animal.prototype现在指向同一个对象。任何对Cat.prototype的改动都会影响Animal.prototype

4 空对象中介

直接继承不行,那么到底有没有好一点的办法啊!我们回头看2

2其实没有什么很大的缺陷,那是因为2中的Cat直接继承了实例,既然继承实例会省去很多麻烦,那我们找一个空对象来接Animal的prototype然后再让cat来继承实例不就好了嘛

    function Animal() {
    
    }
    
    Animal.prototype.species = "动物"
    
    function Cat(name, coloe) {
        this.name = name
        
        this.color = color
    }

    function F() {}
    
    F.prototype = Animal.prototype;
    
    Cat.prototype = new F;
    
    Cat.prototype.constructor = Cat;
    
    Cat.prototype.type = "猫科动物";
    
    Cat.prototype.eat = ()=>{
        alert("吃个毛")
    }

F是空对象,占用内存并不多,而且修改Cat.prototype不会影响到animal

这个真是贼好用,封装个方法吧(根据大神思路封装)

function extend(Child,Parent) {
    let F = function(){}
    
    F.prototype = Parent.prototype
    
    Child.prototype = new F
    
    Child.prototype.constructor = Child
    
    Child.uber = Parent.prototype
}

最后一行大神说是为了做一个父级的备份 uber是德语上层的意思

5 深拷贝

没什么说的,Animal中所有的属性放到prototype中,然后遍历拷贝

  function extend2(Child, Parent) {

    var p = Parent.prototype;

    var c = Child.prototype;

    for (var i in p) {

      c[i] = p[i];

      }

    c.uber = p;

  }

7, 非构造函数的继承

首先 啥是非构造函数的继承

比如说,我现在声明一个对象 叫做 “中国人”

var Chinese = {
    nation: "中国人"
}

我再声明一个职业叫做 “医生”

var Doctor = {
    career: "医生"
}

我现在想让 医生 继承 中国人 但是他俩又只是普通对象,这怎么办

1 object()方法

发明JSON格式的大佬提出的方法

  function object(o) {

    function F() {}

    F.prototype = o;

    return new F();

  }

是构造函数就直接继承,不是构造函数就写个构造函数让他继承

var Doctor = object(Chinese);

Doctor.career = "医生"

这样就实现了刚才想要的效果了

2 浅拷贝

浅拷贝是指只拷贝堆内存的地址

还是拿刚才的案例举例,这次用浅拷贝的方法解决

function extendcopy(p) {
    let o = {}
    
    for (var i in p) {
        o[i] = p[i]
    }
    
    return o
}

var Chinese = {
    nation: "中国"
}

var Doctor = extendcopy(Chinese)

Doctor.career = "医生"

这样就写完了,但是这样有个问题,那就是如果我改一下国家的数据结构

var Chinese = {
    nation: "中国",
    city: [
        "上海","广州","深圳","北京"
    ]
}

然后我们再按照上面的方法进行运算

结果得出

Doctor == {
    career : "医生",
    nation: "中国",
    city: [
        "上海","广州","深圳","北京"
    ]
}

现在我们修改Doctor

Doctor.city.push("青岛")

Chinese == {
    nation: "中国",
    city: [
        "上海","广州","深圳","北京","青岛"
    ]
}

然后我们发现Chinese中的city也变了,这就是这个方法的缺陷,也是浅拷贝的缺陷,因为浅拷贝只拷贝指针

3 深拷贝

所谓"深拷贝",就是能够实现真正意义上的数组和对象的拷贝。它的实现并不难,只要递归调用"浅拷贝"就行了。

  function deepCopy(p, c) {

    var c = c || {};

    for (var i in p) {

      if (typeof p[i] === 'object') {

        c[i] = (p[i].constructor === Array) ? [] : {};

        deepCopy(p[i], c[i]);

      } else {

         c[i] = p[i];

      }
    }

    return c;
  }

8, ES6的面向对象

ES6中多了一个class声明,但是本质上这就是个语法糖,让class结构看起来更清晰,其实就是跟构造函数是一样的。

function Cat(name, color) {
    this.name = name
    
    this.color = color
}

Cat.prototype.eat = ()=>{
    alert("eat")
}

class Cat {
    constructor(name, color) {
        this.name = name
        
        this.color = color
    }
    
    eat() {
        alert("eat")
    }
}

这两个写法本质上讲是一样的。声明实例的时候也是使用new

不同点在于

1 每个class都必须有一个constructor方法,每次声明实例的时候都会运行
2 没有变量提升
3 class中的constructor方法中的 super()方法,是父类的constructor方法,所以说如果父级的constructor有参数,实例中的super()也应该传对应的参数。
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值