前端 JavaScript 设计模式前奏--面向对象-Class类

Posted 黑木令

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端 JavaScript 设计模式前奏--面向对象-Class类相关的知识,希望对你有一定的参考价值。

面向对象–class 类

1. 类 的声明

1.1 方式一: 传统的使用 构造函数 的方式, 模拟一个类的方式

/**
 * 1. 方式一: 传统的使用 构造函数 的方式, 模拟一个类的方式
 *
 * 1. 这里我们就声明了一个 Animal1 类 。
 */
function Animal1 () 
  // 通过 this 来表明这是一个构造函数 。
  this.name = 'name'

console.log('Animal1: ', Animal1)

1.2 方式二: ES6 中对 class 的声明

/**
 * 1. 方式二: ES6 中对 class 的声明 。
 *
 * 1. ES6 中增加了对类的声明 class 语法, AnimalES1 就是类名
 */
class AnimalES1 
  // constructor 就是构造函数
  constructor () 
    this.name = name
  

console.log(AnimalES1)

2. 实例化 类 的对象

1. 如何通过一个类, 来实例化生成一个对象
   1. 虽然声明方式不一样, 但实例化的方式一样 。
   2. 如果构造函数没有参数的话, new 实例化的时候 () 是可以省略的 。

2.1 方式一

/**
 * 1. 方式一:
 */
function Animal2 () 
    this.name = 'name'

console.log(new Animal2()) // name: "name"

2.2 方式二

/**
 * 2. 方式二:
*/
class AnimalES2 
  constructor () 
    this.name = name
  

console.log(new AnimalES2())

3. 关于类的继承: 实现继承的基本原理就是 原型链, 继承的本质就是 原型链 。

3.1 第一方式: 借助 构造函数 实现继承

/**
 * 第一方式: 借助 构造函数 实现继承 。
 */
function Parent1() 
  this.name = 'Parent1';
  this.sec = 'man'

// 这里是在 Parent1 原型对象 上定义的一个方法 。
Parent1.prototype.num = function () 
  // 借助构造函数实现继承 , 的缺点:
  // 构造函数它是有自己的原型链的, prototype 属性 。
  // 任何一个函数都有 prototype 属性, 只有在构造函数中才能真正的起到作用 。
  // 我们使用 call() / aplay() 是为了改变 Parent1 运行时的 this 指向, 也就是
  // 指向了 Child1 实例上, 但是 Parent1 它原型链上的东西, 并没有被 Child1
  // 所继承
  console.log('Parent1.prototype.num')

function Child1() 
  // 在子类的构造函数中直接执行父级构造函数
  // 通过这样的方法实现继承 , 它的原理是什么 ?
  // 父级构造函数 Parent1 , 在 Child1 这个函数中执行 , 重点是 call() 方法 ,
  // 也就是把父级 Parent1 这个函数 , 在子函数 Child1 中执行 ,修改了父级 Parent1
  // 构造函数 this 的指向 , 也就是指向了 Child1 这个构造函数的实例化的引用 , 也
  // 就是 this ;
  // 说白了就是改变 this 的指向 , 从而导致了父类执行的时候这些属性 , 都会挂在到
  // Child1 这个类的实例上去 。
  // call()  、 aplay() 这两种方法都可以 ,它们改变的是函数运行的上下文 。
  Parent1.call(this);
  this.type = 'Child1'

console.log(new Child1) // name: "Parent1", sec: "man", type: "Child1"
console.log(new Child1().num()) // 报错: num is not a function

3.2 方式 二: 借助 原型链 实现继承 。

/**
 * 1. 方式 二: 借助 原型链 实现继承 。
 *
 * 1. prototype 属性的作用就是为了让这个构造函数的实例, 能访问到它的 原型对象 上 。
 * 2. 每一函数都是有 prototype 属性的, 这个属性它是一个对象, 这个对象是可以任意赋值的, 现在赋值给 Parent2 一个实例 。
 */

function Parent2() 
    this.name = 'Parent2';
    this.sec = [1,2,3]

function Child2() 
    this.type = 'Child2'

/**
 * 1. new Parent2() 是一个对象, 给了 Child2 的 prototype 属性, 那现在实例化了 Child2 生成一个新的对象, 这个对象上有 __proto__ 属性, 这个 __proto__ 属性, 就等于 Child2.prototype 因为它也是一个对象
 *
 * 2. Child2.prototype  对象赋值给 new Parent2() 实例, 所以 new Child2().__proto__ 引用的就是 new Parent2() 一个实例对象 。
 */

/**
 * 1. 按照之前类似于 作用域 的寻找方式, 我们先找 new Child2() 上的属性, 比如 name;
 *
 * 2. 那么通过 Child2 实例化的属性上, 是没有 name 的, 没有 name 的话, 它的作用域向这个实例化的 __proto__ 属性上去找, 这个属性是一个对象, 也就是 Child2.prototype 属性;
 *
 * 3. 这个属性又被赋值为 new Parent2() 父类的一个实例, 按照刚才的找法 new Child2().__proto__ 属性其实也就是找到了, new Parent2() 父类的一个对象, 父类的对象上已经有 name 这个属性值了;
 *
 * 4. new Child2() 这个对象就能拿到这个 name, 也就实现了继承 。
 */
Child2.prototype = new Parent2()
console.log( new Child2() )
/**
 * Child2 type: "Child2"
 *   __proto__: Parent2 它指的是 Child2 实例的 原型对象 是 Parent2
 */
console.log( new Child2().__proto__ ) //Parent2 name: "Parent2", sec: Array(3)
console.log( new Child2().__proto__ === Child2.prototype ) //true
// 验证继承的效果 。
console.log( new Child2().__proto__.sec ) //(3) [1, 2, 3]

/**
 * 2. 这个继承方式的缺点:
 *
 * 1. 在一个类上我们实例了两个对象, 当我们改变其中的一个实例的属性值, 另一个也跟着改变;
 * 2. 这不是我们想要的结果, 它们应该是隔离的才是我们想要的效果, 如果我们在一个项目中,
 * 3. 同一个类实例了几个对象, 当我们修改其中一个实例的属性值, 另一个实例也跟着去改变,
 * 4. 这样我们就完全没有必要使用面向对象了 。
 * 5. 这个缺点形成的原因: 因为原型链中的原型对象, 实例对象对它们是公用的, 我们可以使用 s1.__proto__ === s2.__proto__ 来验证, 它们是严格相等的, 因为它们引用的是同一个对象, 也就是父类的实例对象 。
 */
var s1 = new Child2()
var s2 = new Child2()
console.log(s1.sec , s2.sec )//[1, 2, 3]  [1, 2, 3]
s1.sec.push(4)
console.log(s1.sec , s2.sec )//(4) [1, 2, 3, 4] (4) [1, 2, 3, 4]
console.log(s1.__proto__ === s2.__proto__) //true

3.3 方式 三: 组合类 构造函数 和 原型链 结合使用

/**
 * 3. 方式 三: 组合类 构造函数 和 原型链 结合使用 。
 *    1. 这个是实现继承最通用的方式 。
 */
/**
 * 1. 缺点:
 *
 * 1. 父级构造函数执行了两次 Parent3.call(this) 一次; Child3.prototype = new Parent3() 一次 。
 */
function Parent3() 
  this.name = 'Parent3'
  this.sec = 'man',
  this.play = [1, 2, 3]

function Child3() 
    // 使用 构造函数 的方式实现继承: 在子构造函数中, 使用父级构造函数 。
    Parent3.call(this);
    this.type = 'Child3';

// 使用 原型链 的方式实现继承 。
Child3.prototype = new Parent3();
// 为了验证是否解决了 原型链继承方式的缺点 。
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4)
// console.log(s3, s4)
console.log(s3.play, s4.play) //[1, 2, 3, 4] (3) [1, 2, 3]
3.31 组合方式的优化 1
/**
 * 1. 组合方式的优化 1.
 */
function Parent4() 
  this.name = 'Parent4'
  this.sec = 'man',
  this.play = [1, 2, 3]

function Child4() 
  // 使用 构造函数 的方式实现继承: 在子构造函数中, 使用父级构造函数 。
  Parent4.call(this);
  this.type = 'Child4';

// 使用 原型链 的方式实现继承 。
// Child4.prototype = new Parent4();
// 这里是优化的操作方法; 这里只是一个简单的引用 。
Child4.prototype = Parent4.prototype;
// 为了验证是否解决了 原型链 继承方式的缺点 。
var s5 = new Child4();
var s6 = new Child4();
s5.play.push(4)
console.log(s5.play, s6.play) //(4) [1, 2, 3, 4] (3) [1, 2, 3]
/**
 * 1. 缺点:
 *
 * 1. instanceof 判断 s5 这个对象, 是不是 Child4 这个类的实例 。
 */
console.log(s5 instanceof Child4) //true
console.log(s5 instanceof Parent4) //true
console.log(s5.constructor) // Parent4
3.32 组合方式的优化 2
/**
 * 2. 组合方式的优化 2.
 */
function Parent5() 
  this.name = 'Parent5'
  this.sec = 'man',
  this.play = [1, 2, 3]

function Child5() 
  Parent5.call(this);
  this.type = 'Child5';

// Child4.prototype = new Parent4();
// 这里是优化的操作方法 ; 这里只是一个简单的引用 。
// Child5.prototype = Parent5.prototype;
// 这里是优化的操作方法
Child5.prototype = Object.create(Parent5.prototype);
Child5.prototype.constructor = Child5;
// 为了验证是否解决了 原型链继承方式的缺点 。
var s7 = new Child5();
var s8 = new Child5();
s5.play.push(4)
console.log(s7.play, s8.play) //(4) [1, 2, 3, 4] (3) [1, 2, 3]
console.log(s7 instanceof Child5) //true
console.log(s7 instanceof Parent5) //true
console.log(s7.constructor) // Child5

之前有整理过部分知识点, 现在将整理的相关内容, 验证之后慢慢分享给大家; 这个专题是 “前端ES6基础” 的相关专栏; 不积跬步,无以至千里, 戒焦戒躁 。

《前端设计模式》专栏上篇: 面向对象-封装、继承、多态

《前端设计模式》专栏下篇: 面向对象JQ实例与总结

如果对大家有所帮助,可以点个关注、点个赞; 文章会持续打磨 。
有什么想要了解的前端知识, 可以在评论区留言, 会及时分享所相关内容 。

以上是关于前端 JavaScript 设计模式前奏--面向对象-Class类的主要内容,如果未能解决你的问题,请参考以下文章

前端 JavaScript 设计模式前奏--面向对象-封装继承多态

前端 JavaScript 设计模式前奏--面向对象JQ实例与总结

前端 JavaScript 设计模式前奏--面向对象-Class类

前端 JavaScript 设计模式前奏--面向对象-Class类

前端 JavaScript 设计模式前奏--面向对象JQ实例与总结

前端 JavaScript 设计模式前奏--面向对象JQ实例与总结