前端 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类