【设计模式】学习JS设计模式?先掌握面向对象!

2022-12-10 11:12:54 浏览数 (2)


设计模式

今天开始初学设计模式,在此记录以便日后复习。

什么是设计模式

一个模式就是一个可重用的方案,可应用于在软件设计中的常见问题,另一种解释就是一个我们如何解决问题的模板 - 那些可以在许多不同的情况里使用的模板。 --w3cschool

我为什么要学习设计模式

  • 高级工程师面试必考知识点
  • 写出更好的代码,设计模式是必经之路
  • 掌握设计模式更容易阅读流行框架源码
  • 想要成为项目负责人,从零架构工程,设计模式是基石
  • 没事装个逼
  • ......我学习设计模式的四个阶段
  • 巩固面向对象相关知识,es6 class语法,封装,继承,多态等
  • 掌握设计原则
  • 掌握设计模式
  • 最后做一个练习案例面向对象 学习设计模式,面向对象是基础,那就先来复习一下面向对象和函数式编程的区别 数据存放方式
  • 对于面向对象,数据存放在对象的属性里面,以及全局变量。
  • 对于函数式,数据存放在各级作用域里面,作用域包括全局作用域。

数据访问方式

数据存放方式决定了访问的方式。

  • 对于面向对象来说,访问数据(全局变量除外)需要先获取对象的引用,然后再进行操作(直接访问——公共属性,或者调用成员函数/方法访问——私有属性)
  • 对于函数式,访问数据是直接访问(通过函数入参或者作用域链查找)基于类的面向对象和基于原型的面向对象 基于类的面向对象 类定义了所有用于具有某一特征对象的属性。类是抽象的事物,而不是其所描述的全部对象中的任何特定的个体。另一方面,一个实例是一个类的实例化,是其中的一个成员

基于原型的面向对象

构造函数(constructor),实例(instance),原型(prototype)本身都是对象。基于原型的语言具有所谓的原型对象的概念,新对象可以从中获得原始的属性。

JavaScript中有一个很有意思的__proto__属性用于访问其原型对象,构造函数,实例,原型本身都有__proto__指向原型对象。最后顺着原型链都会指向Object这个构造函数,然而Object的原型对象的原型是null

通过原型添加方法
代码语言:javascript复制
Student.prototype.study = function() {
  console.log('我在学习'   this.subject);
}
为什么要将方法挂载到原型上?

对于每一个实例对象,其方法的函数体是一模一样的,方法的执行结果只根据其实例对象决定,然而生成的每个实例都需要生成一个方法去占用一份内存。

ES6之前的面向对象

ES6之前创建对象有两种方式,这两种方式在特定的使用场景分别有其优点和缺点, 下面我们来分别介绍这两种创建对象的方式。

  • 第一种是使用对象字面量的方式
  • 第二种是使用构造函数的方式使用对象字面量的方式 我们通过对象字面量的方式创建两个student对象,分别是student1student2
代码语言:javascript复制
var student1 = {
  name: '小呆',
  age: 22,
  subject: '前端开发'
};

var student2 = {
  name: '小傻',
  age: 22,
  subject: '后端开发'
};

对象字面量的方式在创建单一简单对象的时候是非常方便的。但是,它也有其缺点:

  • 在生成多个实例对象时, 我们需要每次重复写很多属性,写起来特别的麻烦。
  • 虽然都是学生的对象, 但是看不出两个对象之间有什么联系。使用构造函数的方式 构造函数就其实就是一个普通的函数,当对构造函数使用new进行实例化时,会将其内部this的指向绑定到实例对象上.

我们来创建一个Student构造函数(构造函数通常使用大写字母开头,和普通函数做区分)。

代码语言:javascript复制
function Student (name, age, subject) {
  this.name = name;
  this.age = age; 
  this.subject = subject;
  console.log(this);
}

我们在构造函数中打印出this的指向。因为构造函数其实就是一个普通的函数, 那么我们便可以使用普通函数的调用方式去调用Student

代码语言:javascript复制
Student('小呆', 22, '前端开发'); //window{}

可以看到,采用普通方式调用Student时, this的指向是window。下面使用new来实例化该构造函数, 生成一个实例对象student1

代码语言:javascript复制
let student1 = new Student('小呆', 22, '前端开发'); 
//Student {name: "阿辉", age: 22, subject: "前端开发"}

可以看到,当我们采用new生成实例化对象student1时, this不再指向window, 而是指向的实例对象本身。

上面的就是采用构造函数的方式生成实例对象的方式, 并且当我们生成其他实例对象时,由于都是采用Student这个构造函数实例化而来的, 我们能够清楚的知道各实例对象之间的联系。

new都做了什么?
  • 创建一个新对象
  • 分配内存,将构造函数的作用域赋值给新对象,即this指向这个新对象
  • 执行构造函数中的代码
  • 返回新对象

ES6中的面向对象

ES6中提供了基于类class的语法。但class本质上是ES6提供的一颗语法糖,但实际上JavaScript是一门基于原型的面向对象语言。

ES6中使用class来创建对象

代码语言:javascript复制
//定义类
class Student {
  //构造方法
  constructor(name, age, subject) {
    this.name = name;
    this.age = age;
    this.subject = subject;
  }

  //类中的方法
  study(){
    console.log('我在学习'   this.subject);
  }
}
//实例化类
let student3 = new Student('小痴', 24, '前端开发');
student3.study(); //我在学习前端开发

上面的代码定义了一个Student类, 可以看到里面有一个constructor方法, 这就是构造方法,而this关键字则代表实例对象。也就是说,ES5中的构造函数Student, 对应的是Es6Student类中的constructor方法。

Student类除了构造函数方法,还定义了一个study方法。方法之间不要用逗号分隔,加了会报错。而且,类中的方法全部是定义在原型上的,我们可以用下面的代码进行验证。

代码语言:javascript复制
console.log(student3.__proto__.study === Student.prototype.study); //true
console.log(student3.hasOwnProperty('study')); // false

上面的第一行的代码中, student3.__proto__是指向的原型对象,其中Student.prototype也是指向的原型的对象,结果为true就能很好的说明类中的方法全部是定义在原型上的。

第二行代码是验证student3实例中是否有study方法,结果为false, 表明实例中没有study方法,这也更好的说明了上面的结论。

而将study方法挂载到prototype原型对象,所有的实例都能继承该方法,多个实例的方法指向了同一个内存地址。

实例与类

javascript中实例方法与类方法的区别

  • 类属性(静态属性):通过类直接访问,不需要声明类的实例来访问
  • 类方法(静态方法):通过类直接访问,不需要声明类的实例来访问
  • 实例属性(动态属性):不能通过类直接访问,需要通过该类声明的实例才能访问
  • 实例方法(动态方法):不能通过类直接访问,需要通过该类声明的实例才能访问
代码语言:javascript复制
Person = function(){
}
Persion.sex = "woman";  //类属性
Person.eat= function(){  //类方法
  alert("eat");
}
Person.prototype.age = 10; //实例属性
Person.prototype.dance = function(){ //实例方法
  alert("dance");
}
const person = new Person();
person.age;//实例属性
person.dance();//实例方法
Person.sex;//静态属性
Person.eat();//静态方法

ES6中的实例与类

静态属性和实例属性

静态属性需要使用static关键字
代码语言:javascript复制
class Foo {
  static num = 1;
    age = 2
}
const foo = new Foo();
console.log(Foo.num);// 1
console.log(foo.num);// undefined
console.log(Foo.age);// undefined
console.log(foo.age);// 2

如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。

静态方法需要使用static关键字
代码语言:javascript复制
class Foo {
  static classMethod() {
    return 'hello';
  }
}

Foo.classMethod() // 'hello'

const foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
静态方法可以与非静态方法重名

如果静态方法包含this关键字,这个this指的是类,而不是实例。

代码语言:javascript复制
class Foo {
  static bar() {
    this.baz();
  }
  static baz() {
    console.log('hello');
  }
  baz() {
    console.log('world');
  }
}
const foo = new Foo();
Foo.bar() // hello
foo.bar() // world
父类的静态方法,可以被子类继承。
代码语言:javascript复制
class Foo {
  static classMethod() {
    return 'hello';
  }
}

class Bar extends Foo {
}

Bar.classMethod() // 'hello'
静态方法也是可以从super对象上调用的
代码语言:javascript复制
class Foo {
  static classMethod() {
    return 'hello';
  }
}

class Bar extends Foo {
  static classMethod() {
    return super.classMethod()   ', too';
  }
}

Bar.classMethod() // "hello, too"

封装,继承,多态

面向对象三大特性:封装继承多态

封装

封装就是将数据操作的细节隐藏起来,只暴露对外的接口,外界调用端不需要也不可能知道细节,只能通过通过对外暴露的接口来访问该对象

代码语言:javascript复制
class School {
  // 构造器:创建对象完成初始化操作
  constructor(id, name) {
    this.id = id
    this.name = name
  }
  // 实例方法
  schoolName() {
    console.log(this.name)
  }
  // 类方法
  static schoolOnly() {
    console.log('我是类方法只可通过函数本身调用')
  }
}

继承

继承表示子类继承父类,子类除了拥有父类所有的特征以外,还有一些更具体的特性;

代码语言:javascript复制
class Student extends School {
  constructor(sId, sName, id, name) {
    super(id, name)
    this.sId = sId
    this.sName = sName
  }
  studentName() {
    console.log(this.sName)
  }
  say() {
    console.log('I am a student')
  }
}

多态

由继承产生的多个不同的类,对同一个方法可以有不同的响应,比如学生和老师都可以继承自学校,但是它们分别实现了自己的say方法;此时针对某一个实例,我们无需了解他是学生还是老师,我们可以直接调用say方法,程序会自动判断应该如何执行这个方法;

代码语言:javascript复制
class Teacher extends School {
  constructor(tId, tName, id, name) {
    super(id, name)
    this.tId = tId
    this.tName = tName
  }
  TeacherName() {
    console.log(this.tName)
  }
  say() {
    console.log('I am a teacher')
  }
}
测试
let school = new School(1, '第一小学')
let student = new Student(10, 'Daming', 1, '第一小学')
let teacher = new Teacher(100, 'MrLi', 1, '第一小学')
console.log(student) //Object  Student {id: 1, name: "第一小学", sId: 10, sName: "Daming"}
console.log(teacher) //Object  Teacher {id: 1, name: "第一小学", tId: 100, tName: "MrLi"}
student.studentName() //Daming
student.schoolName()  //第一小学
student.say() //I am a student
teacher.say() //I am a teacher
School.schoolOnly() //我是类方法只可通过函数本身调用

小伙伴们觉的对你有帮助的请点赞

0 人点赞