JavaScript设计模式-00面向对象

什么是面向对象

概念

类可以理解为是模板,我们看下面的实例

class People {
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
    eat(){
        alert(`${this.name} eat something`);
    }
    speak(){
        alert(`My name is ${this.name},age ${this.age}`)
    }
}
  1. 上述是 ES6 的语法,但在 ES5 中可以用构造函数去实现。
  2. 它定义了一个 constructor 方法,表述了如果要实例化这个模板需要哪些条件,在这个例子中它告诉了我们需要 nameage两个参数,也就是说, 它对人的定义就是要有名字和年龄 ,而this.name = namethis.age = age是把这两个参数写进了它的属性里,它是一个对象的特性
  3. 同时,人除了名字和年龄外,它还有很多的动作,有很多能做的事,而这个模板给出的定义是它可以eat也可以speak,或者喝水睡觉打人骂人等等,在模板内部定义了这些动作的具体如何实现。
  4. 所以,我们可以说 类是由属性和方法(函数,动作)构成的

对象

对象即实例,来看下面的具体实现

let zhang = new People('zhang',20);
zhang.eat();
zhang.speak();

let wang = new People('wang',21);
wang.eat();
wang.speak();
  1. 从上面可以看到,我们用一个 People 模板去实例化了两个对象,它当然可以实例化很多个对象,它们不一定是相同的,所以模板和对象是 一对多 的关系
  2. 通过let zhang = new People('zhang',20);去创建了一个 出来,我们还记得人的构造函数里需要名字和年龄,现在我们告诉它,名字是zhang年龄是20,这样它就帮我们做出来了一个人 zhang, zhang 就可以吃饭也可以说话了,同理,wang 也可以吃饭和说话了。

三要素

这是面向对象里唯一的基础又重要的概念了,面向对象的三要素

继承

​ 子类继承父类(在 JavaScript 中较常用)

​ 在上面我们已经定义了一个类 Person,但话说回来,人是一个很抽象很宽泛的概念,比如学生是人,教师是人,职员是人,甚至男人和女人也是有很大区别,那么只用一个 Person 类肯定是无法描述全部的,那么我们就需要对人这个抽象的概念丰富其中的细节

class Students extends People {
    constructor(name,age,number){
        super(name,age);
        this.number = number;
    }
    study(){
        alert(`${this.name} study`)
    }
}

​ 我们定义了一个学生类继承自(extends关键字)People 类,它的 constructor 需要名字,年龄和学号,作为一个学生它 首先是一个人 ,学号可能是学生独有的,其他人可能没有,但名字和年龄是一定有的,所以我们把 定义一个人的工作交给 People 类去做,Students 类只负责在人的基础上定义学生独有的特征。

​ 所以我们可以看到名字和年龄被传给了super函数,这个函数名字可能不太好理解,在一些场合,父类也被称为超类,所以 super 指的就是它的父类

​ 把名字和年龄交给父类以后,我们就要干我们自己的活了,把学号写在属性里,然后我们再给学生定义一个学习的动作。

​ 至此,学生的模板就做好了,那么我们来想一想:学生能否吃饭和说话呢?答案是肯定的,学生 首先是一个人然后才是一个学生,它当然有人的特征和行为。

​ 现在我们来看看它应该如何使用

let xiaoming = new Students('xiaoming',10,'A1')
xiaoming.study()
console.log(xiaoming.number)
xiaoming.eat()

let xiaohong = new Students('xiaohong',11,'A2');
xiaohong.study();
xiaohong.speak();
  1. People 是父类,公共的,不仅仅服务于 Student
  2. 继承可将公共方法抽离出来,提高复用,减少冗余

封装

​ 数据的权限和保密(这个在 JavaScript 中几乎用不到,ES6 和 ES5 都 没有 对封装提供语法层面的支持

  1. public 完全开放
  2. protected 对子类开放
  3. private 对自己开放

这里再强调一下,JavaScript 是有封装特性的,但没有以上这三个关键字,它的封装是通过其他的方式实现的,而不是语法本身的层面实现的,这里我们用TypeScript进行演示。

class People{
    name        //TypeScript要求先声明属性,和其他的OO语言一致
    age         
    protected weight      // 自己和子类可以访问
    constructor(name,age){
        this.name = name
        this.age = age
        this.weight = 120
    }
    eat(){
        console.log(`${this.name} eat something`);
    }
    speak(){
        console.log(`My name is ${this.name},age ${this.age}`)
    }
}

class Student extends People{
    number
    private girlfriend         //只有自己可以用
    constructor(name,age,number){
        super(name,age)
        this.number = number
        this.girlfriend = 'xiaoli'
    }
    study(){
        console.log(`${this.name} study`)
    }
    getWeight(){
        console.log(`${this.weight}`)
    }
}

可以看到,TypeScript的语法和 JavaScript 的语法基本一致,它其实就是JavaScript的超集

现在我们来编写测试类:

let xiaoyang = new Student('xiaoyang',14,'D2')
xiaoyang.getWeight()    //120

以上代码可以正常运行并得到预期结果,但如果尝试通过 xiaoyang 获取 girlfriend:

console.log(xiaoyang.girlfriend)  //被禁止

那么就会抛出一个错误,因为 girlfriend 只能被 student 类自己在内部去用,外界不能。

同理,在 People 类中定义的 protected weight 也是不能通过 xiaoyang 访问的,但可以通过提供的 getWeight()方法去获取

  1. 减少耦合,不该外露的不外露
  2. 利于数据,接口的权限管理
  3. ES6 目前不支持,一般认为,下划线(_)开头的属性是 private

多态

​ 同一接口的不同实现(在 前端 JS 中用的很少,因为前端没有接口的概念)

​ 受 JavaScript 本身的限制,下面的代码可能没有很好的演示效果,我们稍后会解释:

class People{
    constructor(name){
        this.name = name
    }
    saySomething(){

    }
}

class A extends People {
    constructor(name){
        super(name)
    }
    saySomething(){
        console.log("I am A")
    }
}

class B extends People {
    constructor(name){
        super(name)
    }
    saySomething(){
        console.log("I am B")
    }
}

let a = new A('a')
a.saySomething()
let b = new B('b')
b.saySomething()

多态的体现是可以用父类型去接收子类型的实例,但因为 js 定义变量都是 const,let 或者 var,无需声明类型,所以我们体现不出来多态性的意义,只能做个简单的演示

小结

虽然 JavaScript 中继承的特性用的较多,封装和多态的特性用的较少,但这三样我们仍然都需要去掌握。

JavaScript 的应用

  1. JQuery 就是一个 class
  2. $('p') 是 JQuery 的一个实例
  3. 以下是 JQuery 的一部分源码
class jQuery {
    constructor(selector){
        let slice = Array.prototype.slice;
        let dom = slice.call(document.querySelectorAll(selector));
        let len = dom ? dom.length : 0;
        for (let i = 0; i < len;i++){
            this[i] = dom[i];
        }
        this.length = len;
        this.selector = selector || ''
    }
    append(node){

    }
    addClass(name){

    }
    html(data){
        
    }
}
  1. 使用时
window.$ = function(selector){
    return new jQuery(selector)
}
var $p = $('p');
console.log($p);
console.log($p.addClass)

意义

为何使用面向对象?

  1. 顺序,判断,循环 —— 结构化
  2. 面向对象 —— 数据结构化
  3. 对于计算机来说,结构化的才是最简单的
  4. 编程应该 简单 & 抽象