背景
最近在学习canvas的使用,并写了个demo,就是做一个K线图,如图所示。写代码过程中,封装是必不可少的一部分,所以想以这个为例子来讲一下封装。
目的
减少代码量。
定义
万物皆对象,对象具有属性和行为(方法),对象公共属性和行为的提取就是封装。
理念
封装的前提,必定跟场景相关联的,也就是说,先有场景,再有封装。封装前,先把场景下的属性具象化出来,再考虑提取他们的共性。如果跳过这一步直接思考他们的共性,这很容易出现思考偏差。
举个例子:一个学校里面,有老师、学生、校长等各角色,不同角色有自己的特点和行为,比如学生有学号,老师有职位等级、类型,他们也有共同的特性,比如拥有性别、年龄等特点,用类(class)来表示各个角色。
将学生和老师用代码抽象化表示:
class Student {
constructor(studentNumber, sex, age) {
this.studentNumber = studentNumber // 学号
this.sex = sex
this.age = age
}
}
class Teacher {
constructor(rank, sex) {
this.rank = rank // 等级
this.sex = sex
this.age = age
}
}
发现学生和老师都有性别和年龄,那就可以提取出来,所以可以很容易用Person封装起来,
class Person {
constructor(sex, age) {
this.sex = sex
this.age = age
}
}
然后就可以使用extends让子类(学生)继承父类(Person)的属性
class Student extends Person {
constructor(studentNumber, sex, age) {
super(sex, age)
// super() 表示执行父类构造函数,相当于Person.prototype.constructor.call(this), 用于继承父类Person的属性
// 在这里,相当于this.sex = undefined; this.age = undefined
// 传入sex,age 相当于 this.sex = sex; this.age = age;
this.studentNumber = studentNumber
}
}
const student1 = new Student('330311221', '男', 15)
// student1 = { age: 15, sex: "男", studentNumber: "330311221" }
class Teacher extends Person {
constructor(rank, sex, age) {
super(sex, age)
this.rank = rank
}
}
const teacher1 = new Teacher('高级教师', '女', 35)
// teacher1 = { age: 35, sex: "女", rank: "高级教师" }
封装颗粒度
封装颗粒度表示函数的拆分程度,是否越细越好呢,其实不是。
在项目中,如果组件拆的过于细,可能会导致父组件的参数向子组件一层层传递时,会出现遗漏,传错等问题。如果拆的太粗,会导致难以复用、难以维护等问题,那么怎样的颗粒度大小才算合适呢?封装的颗粒度大小,取决于不同场景下的偏向性考虑。
考虑偏向性
封装偏向性可以分为两种,偏应用还是偏底层。
偏应用:这个封装只适用你这个特定的场景
偏底层:具有独立性,颗粒度更小,可以脱离特定场景,就像工具方法,如Lodash工具库
工具方法:是一个纯函数,传入参数,返回结果。也就是说不要在工具方法内部获取外部变量,要作为参数传入。
考虑哪种偏向性,取决于场景,所以进入“写一个K线图demo”的场景,来聊一聊封装。
举例说明
可以看到图中的蜡烛出现了10次,那么如何封装渲染蜡烛的这个函数呢?是封装单个蜡烛合适还是一串蜡烛合适?
如果作为单个蜡烛的出现,我们只需要知道蜡烛横坐标、蜡烛宽度、颜色以及四个点的纵坐标就能绘制出蜡烛,代码如下:
/**
* 绘制蜡烛
* @param {number} abscissa 蜡烛横坐标
* @param {number} topPointY 最高点纵坐标
* @param {number} bottomPointY 最低点纵坐标
* @param {number} secondPointY 第二个点纵坐标
* @param {number} thirdPointY 第三个点纵坐标
* @param {number} candleW 蜡烛宽度
* @param {string} candleColor 蜡烛颜色
*/
function renderCandle (abscissa, topPointY, bottomPointY, secondPointY, thirdPointY, candleW, candleColor) {
const halfCandleW = candleW / 2
// 绘制蜡烛上影线
ctx.beginPath()
ctx.moveTo(abscissa, topPointY)
ctx.lineTo(abscissa, secondPointY)
ctx.closePath();
ctx.stroke()
// 绘制蜡烛下影线
ctx.beginPath()
ctx.moveTo(abscissa, bottomPointY)
ctx.lineTo(abscissa, thirdPointY)
ctx.closePath();
ctx.stroke()
// 绘制蜡烛实体(中间矩形部分)
ctx.beginPath()
ctx.moveTo(abscissa - halfCandleW, secondPointY)
ctx.rect(abscissa - halfCandleW, secondPointY, candleW, thirdPointY - secondPointY)
ctx.fillStyle = candleColor
ctx.fill();
}
renderCandle(50, 88, 50, 44, 33, 20, 'red')
这个函数的封装偏向性就属于偏底层封装,它具有独立性,颗粒度更小,复用率更高,就像在Echarts图标库里,有多个应用到单个蜡烛的地方,就可以使用这个工具函数,你只需要传入相关参数即可。
但在我这个练习demo中,单个蜡烛并没有独立出现的场景,我考虑偏应用封装,而且在渲染蜡烛之前我需要处理数据源、判断涨跌以及蜡烛颜色的情况,所以将这些处理逻辑和绘制蜡烛封装为一个函数,不管在阅读还是使用上都更为方便,我的代码为:
/**
* 绘制一串蜡烛
* @param {array} data 数据源
* @param {number} candleW 蜡烛宽度
*/
function renderCandles (data, candleW) {
// 将数据源转换为绘制蜡烛所需要的纵坐标集合
const dataYAxisPoint = tranPriceToOrdinate(data)
const halfCandleW = candleW / 2
for (let i = 0, candleLength = dataYAxisPoint.length; i < candleLength; i++) {
const { heightPrice, lowPrice, openingPrice, closingPice } = dataYAxisPoint[i]
let
abscissa = xAxisTickPointX(i),
topPointY = heightPrice,
bottomPointY = lowPrice,
secondPointY,
thirdPointY,
candleColor
if (closingPice < openingPrice) {
// 涨
candleColor = 'red'
secondPointY = closingPice
thirdPointY = openingPrice
} else {
candleColor = 'green'
secondPointY = openingPrice
thirdPointY = closingPice
}
// 绘制蜡烛上影线
ctx.beginPath()
ctx.moveTo(abscissa, topPointY)
ctx.lineTo(abscissa, secondPointY)
ctx.closePath();
ctx.stroke()
// 绘制蜡烛下影线
ctx.beginPath()
ctx.moveTo(abscissa, bottomPointY)
ctx.lineTo(abscissa, thirdPointY)
ctx.closePath();
ctx.stroke()
// 绘制蜡烛实体(中间矩形部分)
ctx.beginPath()
ctx.moveTo(abscissa - halfCandleW, secondPointY)
ctx.rect(abscissa - halfCandleW, secondPointY, candleW, thirdPointY - secondPointY)
ctx.fillStyle = candleColor
ctx.fill();
}
}
总结
- 封装的理念为:把场景下的属性和方法具象化,提取共性
- 封装的颗粒度大小,取决于不同场景下的偏向性考虑