一张刮刮卡竟包含这么多前端知识点

2023-10-11 15:30

本文主要是介绍一张刮刮卡竟包含这么多前端知识点,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

刮刮卡是大家非常熟悉的一种网页交互元素了。实现刮涂层的效果,需要借助canvas来实现,想必每个前端工程师都清楚。实现刮刮卡并不难,但其中却涉及很多知识点,掌握这些知识点,有助于我们更深刻理解原理,对于提升举一反三的能力很有帮助。本期以实现刮刮卡为例,分享下如何科学合理地封装函数,并对涉及的相关知识点进行讲解。

先看下最终效果:

实现刮刮卡都涉及到哪些知识点呢?

知识点1:canvas元素尺寸与画布尺寸

知识点2:prototype、__proto__、constructor

知识点3:canvas的globalCompositeOperation

知识点4:addEventListener第三个参数的passive属性

知识点5:canvas的ImageData

下面进入本期分享的正式内容。

1 需求分析

为了满足更多的场景需要,我们尽可能地提供更多的参数,方便使用者。先从产品和UI的角度来思考下,一个刮刮卡可能需要哪些配置选项。

  • 涂层样式(图片 or 纯色)

  • 涂抹画笔半径

  • 涂抹到百分之多少时,直接刮开全部涂层

  • 刮开全部涂层的效果(淡出 or 直接消除)

接下来再补充下技术配置选项:

  • canvas元素

  • 屏幕像素显示倍数(适应Retina等高倍屏)

  • 淡出效果的过渡动画时间

OK,确认好以上配置参数后,就可以正式开工了。

2 页面构建

项目目录结构如下:

  1. |- award.jpg <--刮刮卡底层结果页图片

  2. |- index.html

  3. |- scratch-2x.jpg <--刮刮卡涂层图片

  4. |- scratchcard.js

页面结构很简单,div的background显示结果,div里的canvas用来做涂层。

新建index.html,加入以下代码(HTML模板代码略过):

HTML代码:

  1. <div class="card">

  2. <canvas id="canvas" width="750" height="280"></canvas>

  3. </div>

CSS代码:

  1. .card {

  2. width: 375px;

  3. height: 140px;

  4. background: url('award.jpg');

  5. background-size: 375px 140px;

  6. }

  7. .card canvas {

  8. width: 375px;

  9. height: 140px;

  10. }

award.jpg用的是2倍图,因此使用 background-size缩放回1倍显示大小。

这里可以发现,HTML中canvas的width、height与CSS中的width、height不一致。原因就是要适应Retina 2倍屏幕。这里就涉及到了canvas画布尺寸的知识点。

现在页面显示效果如下,结果图像已显示出来:

知识点1:canvas元素尺寸与画布尺寸

HTML中canvas的width、height是画布大小,通俗来讲就是canvas画布的“绘制区域大小”,一定要跟元素的显示大小区别开来。

我们的结果图素材是750x280,所以要让canvas完全绘制这张图片,画布大小也需要是750x280。

那么元素大小,就是canvas在页面的“显示大小”。通过CSS对canvas元素进行宽高设置,使其正确的显示。

3 构建类的雏形

新建scratchcard.js。

结合第1章节的需求分析,类的雏形如下:

  1. function ScratchCard(config) {

  2. // 默认配置

  3. this.config = {

  4. // canvas元素

  5. canvas: null,

  6. // 直接全部刮开的百分比

  7. showAllPercent: 65,

  8. // 图片图层

  9. coverImg: null,

  10. // 纯色图层,如果图片图层值不为null,则纯色图层无效

  11. coverColor: null,

  12. // 全部刮开回调

  13. doneCallback: null,

  14. // 擦除半径

  15. radius: 20,

  16. // 屏幕倍数

  17. pixelRatio: 1,

  18. // 展现全部的淡出效果时间(ms)

  19. fadeOut: 2000

  20. }

  21. Object.assign(this.config, config);

  22. }

使用对象的方式向函数传参有很多优点:

  1. 参数语义化,方便理解

  2. 不用在意参数顺序

  3. 传参的增删和顺序调整不会影响业务代码的使用

使用Object.assign方法,可将传递进来的config参数覆盖默认参数。传递的config中没有的属性,则使用默认配置。

在index.html中引入scratchcard.js,在body最下边插入script代码:

  1. new ScratchCard({

  2. canvas: document.getElementById('canvas'),

  3. coverImg: 'scratch-2x.jpg',

  4. pixelRatio: 2,

  5. doneCallback: function() {

  6. console.log('done')

  7. }

  8. });

刮刮卡的类使用起来非常方便,仅传递不使用默认配置的值即可。

4 实现ScratchCard

4.1 构建ScratchCard原型

继续编写scratchcard.js:

  1. function ScratchCard(config) {

  2. this.config = {

  3. ...(略)

  4. }

  5. Object.assign(this.config, config)

  6. + this._init();

  7. }

  8. + ScratchCard.prototype = {

  9. + constructor: ScratchCard,

  10. + // 初始化

  11. + _init: function() {}

  12. + }

这里设置了constructor: ScratchCard,仅仅是为了显得更加严谨,省略这一行也是没有问题的。

由代码中 prototypeconstructor引出第2个知识点。

知识点2:prototype、__proto__、constructor

先记住两点:

  1. __proto__和 constructor属性是对象所独有的(函数也是对象)。

  2. prototype属性是函数所独有的。

※由于JS中函数也是一种对象,所以函数也拥有 __proto__constructor属性。

【__proto__】

__proto__属性都是由一个对象指向一个对象,即指向它们的原型对象(也可以理解为父对象)。

它的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(父对象)里找,如果父对象也不存在这个属性,则继续在父对象的__proto__属性所指向的对象(爷爷对象)里找,如果还没找到,则继续往上找,直到原型链顶端null。null为原型链的终点。

由以上这种通过__proto__属性来连接对象直到null的一条链即为所谓的原型链

【prototype】

prototype对象是函数所独有的,它是从一个函数指向一个对象。它的含义是函数的原型对象,也就是由这个函数所创建的实例的原型对象。

  1. // 示例代码

  2. var demo = new Demo()

  3. function Demo(config) { ... }

因此,以上代码中,demo.__proto__ === Demo.prototype。

【constructor】

constructor属性也是对象独有的,它是从一个对象指向一个函数。其含义就是指向该对象的构造函数。所有函数最终的构造函数都指向Function。

当创建一个函数的时候,会同时自动创建它的 prototype对象,这个对象也会自动获得 constructor属性,并指向自己。

那么,为什么我们这里还要手动设置constructor: ScratchCard呢?

原因就是我们用这样的语法:

  1. ScratchCard.prototype = {}

会导致自动设置的constructor属性值被覆盖。在这种情况下,如果我们不特意设置constructor: ScratchCard的话,constructor则会指向Object。

4.2 实现canvas涂层

先添加以下代码:

  1. function ScratchCard(config) {

  2. this.config = {

  3. ...(略)

  4. }

  5. Object.assign(this.config, config);

  6. + this.canvas = this.config.canvas;

  7. + this.ctx = null;

  8. + this.offsetX = null;

  9. + this.offsetY = null;

  10. this._init();

  11. }

  12. ScratchCard.prototype = {

  13. constructor: ScratchCard,

  14. // 初始化

  15. _init: function() {

  16. + var that = this;

  17. + this.ctx = this.canvas.getContext('2d');

  18. + this.offsetX = this.canvas.offsetLeft;

  19. + this.offsetY = this.canvas.offsetTop;

  20. + if (this.config.coverImg) {

  21. + // 如果设置的图片涂层

  22. + var coverImg = new Image();

  23. + coverImg.src = this.config.coverImg;

  24. + // 读取图像

  25. + coverImg.onload = function() {

  26. + // 绘制图像

  27. + that.ctx.drawImage(coverImg, 0, 0);

  28. + that.ctx.globalCompositeOperation = 'destination-out';

  29. + }

  30. + } else {

  31. + // 如果没设置图片涂层,则使用纯色涂层

  32. + this.ctx.fillStyle = this.config.coverColor;

  33. + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);

  34. + this.ctx.globalCompositeOperation = 'destination-out';

  35. + }

  36. }

  37. }

初始化代码就是实现涂层的覆盖。这里的关键逻辑是:如果设置了图像涂层,则忽略纯色涂层。

涉及到了canvas两个API:

drawImage用于绘制图像。

fillRect用于绘制矩形,在绘制之前要先设置笔刷,即通过fillStyle属性设置颜色。

这段代码是什么意思呢?

  1. this.ctx.globalCompositeOperation = 'destination-out';

globalCompositeOperation就是第3个知识点。

知识点3:canvas的globalCompositeOperation

在w3school上可以查阅到该属性的详细说明:

描述
source-over默认。在目标图像上显示源图像。
source-atop在目标图像顶部显示源图像。源图像位于目标图像之外的部分是不可见的。
source-in在目标图像中显示源图像。只有目标图像内的源图像部分会显示,目标图像是透明的。
source-out在目标图像之外显示源图像。只会显示目标图像之外源图像部分,目标图像是透明的。
destination-over在源图像上方显示目标图像。
destination-atop在源图像顶部显示目标图像。源图像之外的目标图像部分不会被显示。
destination-in在源图像中显示目标图像。只有源图像内的目标图像部分会被显示,源图像是透明的。
destination-out在源图像外显示目标图像。只有源图像外的目标图像部分会被显示,源图像是透明的。
lighter显示源图像 + 目标图像。
copy显示源图像。忽略目标图像。
xor使用异或操作对源图像与目标图像进行组合。

看上去好像有点懵逼难理解,其实就是类似于指定photoshop里两个图层怎么融合,比如谁遮罩谁、交叉部分消除、交叉部分颜色融合等等。

可以参看下w3school的图示,蓝色为目标图像,红色为源图像。

回到刮刮卡,图片涂层是目标图像,目前源图像还未设置,所以源图像为全透明(源图像的不透明的部分用来抠除目标图像并呈现透明),所以目标图像(图片涂层)全部显示。

现在效果如下图所示,涂层已经覆盖上了。

4.3 添加涂抹事件

涂抹事件,其实就是用touchstart、touchmove、touchend事件,为了顺便兼容鼠标操作,也把mousedown、mousemove、mouseup带上。

修改代码:

  1. function ScratchCard(config) {

  2. this.config = {

  3. ...(略)

  4. }

  5. Object.assign(this.config, config);

  6. this.canvas = this.config.canvas;

  7. this.ctx = null;

  8. this.offsetX = null;

  9. this.offsetY = null;

  10. + // 是否在画布上处于按下状态

  11. + this.isDown = false;

  12. + // 是否已完成刮刮卡

  13. + this.done = false;

  14. this._init();

  15. }

  16. ScratchCard.prototype = {

  17. constructor: ScratchCard,

  18. // 初始化

  19. _init: function() {

  20. ...(略)

  21. this.offsetY = this.canvas.offsetTop;

  22. + this._addEvent();

  23. if (this.config.coverImg) { ...(略) }

  24. },

  25. + // 添加事件

  26. + _addEvent: function() {

  27. + this.canvas.addEventListener('touchstart', this._eventDown.bind(this), { passive: false });

  28. + this.canvas.addEventListener('touchend', this._eventUp.bind(this), { passive: false });

  29. + this.canvas.addEventListener('touchmove', this._scratch.bind(this), { passive: false });

  30. + this.canvas.addEventListener('mousedown', this._eventDown.bind(this), { passive: false });

  31. + this.canvas.addEventListener('mouseup', this._eventUp.bind(this), { passive: false });

  32. + this.canvas.addEventListener('mousemove', this._scratch.bind(this), { passive: false });

  33. + },

  34. + _eventDown: function(e) {

  35. + e.preventDefault();

  36. + this.isDown = true;

  37. + },

  38. + _eventUp: function(e) {

  39. + e.preventDefault();

  40. + this.isDown = false;

  41. + },

  42. + // 刮涂层

  43. + _scratch: function(e) {

  44. + }

  45. }

代码很好理解,就是添加事件监听。当按下的时候,把isDown设置为true,当抬起的时候,把isDown设置为false。

可以看到addEventListener的第3个参数{ passive: false },这是个什么鬼?这就是第4个知识点。

知识点4:addEventListener第三个参数的passive属性

最开始,addEventListener() 的参数约定是这样的:

  1. el.addEventListener(type, listener, useCapture)

  • el:事件对象

  • type:事件类型,click、mouseover 等

  • listener:事件处理函数,也就是事件触发后的回调

  • useCapture:布尔值,是否是捕获型,默认 false(冒泡)

2015年底,为了扩展新的选项,DOM 规范做了修订:

  1. el.addEventListener(type, listener, {

  2. capture: false, // useCapture

  3. once: false, // 是否设置单次监听

  4. passive: false // 是否让阻止默认行为preventDefault()失效

  5. })

三个属性的默认值都为 false。

为什么会多出个passive属性呢?

为了防止页面滚动,很多移动端页面都会监听 touchmove 等 touch 事件,像这样:

  1. document.addEventListener("touchmove", function(e){

  2. e.preventDefault()

  3. })

由于 touchmove 事件对象的 cancelable 属性为 true,也就是说它的默认行为可以被监听器通过 preventDefault() 方法阻止。那它的默认行为是什么呢,通常来说就是滚动当前页面(还可能是缩放页面),如果它的默认行为被阻止了,页面就必须静止不动。但浏览器无法预先知道一个监听器会不会调用 preventDefault(),它能做的只有等监听器执行完后再去执行默认行为,而监听器执行是要耗时的,有些甚至耗时很明显,这样就会导致页面卡顿。即便监听器是个空函数,也会产生一定的卡顿,毕竟空函数的执行也会耗时。

当设置了passtive为true,则会忽略代码中的preventDefault(), 因此页面会变得更流畅。如下演示,右侧手机的页面设置了passtive为true。

OK,那么问题来了?既然默认是passive: false,为什么代码里还要再多此一举写一遍呢?

答案在这里,来看chrome的官方说明:https://www.chromestatus.com/feature/5093566007214080

原文如下:

AddEventListenerOptions defaults passive to false. With this change touchstart and touchmove listeners added to the document will default to passive:true (so that calls to preventDefault will be ignored)..

意思是:addEventListener的option里,默认passive是false。但是如果事件是 touchstart 或 touchmove的话,passive的默认值则会变成true(所以preventDefault就会被忽略了)。

OK,原理讲完了,我们还没有把页面的默认滑动行为阻止掉。不阻止的话,在滑动刮刮卡的时候,页面也会跟着滚动。

4.4 阻止页面滚动

看完了4.3小节,那么阻止页面滚动就很简单了。在index.html的script里加入以下代码:

  1. + window.addEventListener('touchmove', function(e) {

  2. + e.preventDefault();

  3. + }, {passive: false});

  4. new ScratchCard({

  5. ...(略)

  6. });

4.5 实现擦除效果

这里完善下_scratch方法,代码如下:

  1. _scratch: function(e) {

  2. e.preventDefault();

  3. var that = this;

  4. if (!this.done && this.isDown) {

  5. if (e.changedTouches) {

  6. e = e.changedTouches[e.changedTouches.length - 1];

  7. }

  8. var x = (e.clientX + document.body.scrollLeft || e.pageX) - this.offsetX || 0,

  9. y = (e.clientY + document.body.scrollTop || e.pageY) - this.offsetY || 0;

  10. with(this.ctx) {

  11. beginPath()

  12. arc(x * that.config.pixelRatio, y * that.config.pixelRatio, that.config.radius * that.config.pixelRatio, 0, Math.PI * 2);

  13. fill();

  14. }

  15. }

  16. }

逻辑大致如下:

  1. 判断刮刮卡还没刮完(this.done为false),并且处于按下状态(this.isDown为true)。

  2. 如果存在多个触点,则使用最后一个触点。通过e.changedTouches获取最后一个触点。

  3. 计算触点在canvas里的相对坐标。

  4. 在canvas中的触点位置绘制圆形。

需要说明的是,乘以pixelRatio是为了适应多倍屏幕。在本示例中,画布尺寸是2倍尺寸,而坐标是按照网页元素的尺寸计算出来的,正好相差一倍,所以要乘以pixelRatio(pixelRatio = 2)。

还记得4.2小节讲的globalCompositeOperation么?当设置为 destination-out的时候,源图像的非透明部分会抠去目标图像,因此实现了刮刮卡的刮涂层效果。

4.6 检测涂层的透明部分占比

虽然刮涂层的效果实现了,但是还要实时检测刮开了多少,来判断是否完成刮刮卡。

继续修改代码:

  1. _scratch: function(e) {

  2. ...(略)

  3. if (!this.done && this.isDown) {

  4. ...(略)

  5. with(this.ctx) {

  6. ...(略)

  7. }

  8. + if (this._getFilledPercentage() > this.config.showAllPercent) {

  9. + this._scratchAll()

  10. + }

  11. }

  12. }

  13. + // 刮开全部涂层

  14. + _scratchAll() {

  15. + var that = this;

  16. + this.done = true;

  17. + if (this.config.fadeOut > 0) {

  18. + // 先使用CSS opacity清除,再使用canvas清除

  19. + this.canvas.style.transition = 'all ' + this.config.fadeOut / 1000 + 's linear';

  20. + this.canvas.style.opacity = '0';

  21. + setTimeout(function() {

  22. + that._clear();

  23. + }, this.config.fadeOut)

  24. + } else {

  25. + // 直接使用canvas清除

  26. + that._clear();

  27. + }

  28. + // 执行回调函数

  29. + this.config.doneCallback && this.config.doneCallback();

  30. + },

  31. + // 清除全部涂层

  32. + _clear() {

  33. + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);

  34. + },

  35. + // 获取刮开区域百分比

  36. + _getFilledPercentage: function() {

  37. + let imgData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);

  38. + let pixels = imgData.data;

  39. + let transPixels = [];

  40. + for (let i = 0; i < pixels.length; i += 4) {

  41. + if (pixels[i + 3] < 128) {

  42. + transPixels.push(pixels[i + 3]);

  43. + }

  44. + }

  45. + return (transPixels.length / (pixels.length / 4) * 100).toFixed(2)

  46. + }

  47. }

新增了3个方法:

_scratchAll: 清空涂层(全部刮开)。如果设置的fadeOut(淡出时间),则通过CSS动画,将canvas做淡出效果,然后再清除涂层。如果fadeOut为0,则直接清除涂层。

_clear:清除涂层。很简单,直接画一个铺满画布的矩形即可。

_getFilledPercentage:计算刮开区域的百分比。通过遍历canvas每个像素点,计算全透明像素的占比。

这里就涉及到了第5个知识点。

知识点5:canvas的ImageData

利用canvas的getImageData()方法可以获取到全部的像素点信息,返回数组格式。数组中,并不是每个元素代表一个像素的信息,而是每4个元素为一个像素的信息。例如:

data[0] = 像素1的R值,红色(0-255)

data[1] = 像素1的G值,绿色(0-255)

data[2] = 像素1的B值,蓝色(0-255)

data[3] = 像素1的A值,alpha 通道(0-255; 0 透明,255完全可见)

data[4] = 像素2的R值,红色(0-255)

...

本例的透明度不存在中间值,所以就可以认为alpha小于128即为透明。

4.7 注意事项

由于浏览器安全限制,Image不能读取本地图片,因此需要部署在服务端,以http协议浏览本项目。

以上就是本期分享的全部内容了。完整代码请前往GitHub:https://github.com/Yuezi32/scratchcard

看似简单的刮刮卡却隐藏了这么多的知识点,你都掌握了么?

❤️ 看完三件事

如果你觉得这篇内容对你挺有启发,我想邀请你帮我三个小忙:

  • 点个【在看】,或者分享转发,让更多的人也能看到这篇内容

  • 关注公众号【全栈前端精选】,不定期分享原创&精品技术文章。

  • 公众号内回复:【 1 】。加入全栈前端精选公众号交流群。

欢迎评论区留下你的精彩评论~

觉得文章不错可以分享到朋友圈让更多的小伙伴看到哦~

客官!在看一下呗

这篇关于一张刮刮卡竟包含这么多前端知识点的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/189028

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

【 html+css 绚丽Loading 】000046 三才归元阵

前言:哈喽,大家好,今天给大家分享html+css 绚丽Loading!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 📚一、效果📚二、信息💡1.简介:💡2.外观描述:💡3.使用方式:💡4.战斗方式:💡5.提升:💡6.传说: 📚三、源代码,上代码,可以直接复制使用🎥效果🗂️目录✍️

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

基本知识点

1、c++的输入加上ios::sync_with_stdio(false);  等价于 c的输入,读取速度会加快(但是在字符串的题里面和容易出现问题) 2、lower_bound()和upper_bound() iterator lower_bound( const key_type &key ): 返回一个迭代器,指向键值>= key的第一个元素。 iterator upper_bou

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能

Vue3项目开发——新闻发布管理系统(六)

文章目录 八、首页设计开发1、页面设计2、登录访问拦截实现3、用户基本信息显示①封装用户基本信息获取接口②用户基本信息存储③用户基本信息调用④用户基本信息动态渲染 4、退出功能实现①注册点击事件②添加退出功能③数据清理 5、代码下载 八、首页设计开发 登录成功后,系统就进入了首页。接下来,也就进行首页的开发了。 1、页面设计 系统页面主要分为三部分,左侧为系统的菜单栏,右侧

【VUE】跨域问题的概念,以及解决方法。

目录 1.跨域概念 2.解决方法 2.1 配置网络请求代理 2.2 使用@CrossOrigin 注解 2.3 通过配置文件实现跨域 2.4 添加 CorsWebFilter 来解决跨域问题 1.跨域概念 跨域问题是由于浏览器实施了同源策略,该策略要求请求的域名、协议和端口必须与提供资源的服务相同。如果不相同,则需要服务器显式地允许这种跨域请求。一般在springbo

HTML提交表单给python

python 代码 from flask import Flask, request, render_template, redirect, url_forapp = Flask(__name__)@app.route('/')def form():# 渲染表单页面return render_template('./index.html')@app.route('/submit_form',

两个月冲刺软考——访问位与修改位的题型(淘汰哪一页);内聚的类型;关于码制的知识点;地址映射的相关内容

1.访问位与修改位的题型(淘汰哪一页) 访问位:为1时表示在内存期间被访问过,为0时表示未被访问;修改位:为1时表示该页面自从被装入内存后被修改过,为0时表示未修改过。 置换页面时,最先置换访问位和修改位为00的,其次是01(没被访问但被修改过)的,之后是10(被访问了但没被修改过),最后是11。 2.内聚的类型 功能内聚:完成一个单一功能,各个部分协同工作,缺一不可。 顺序内聚: