设计模式通常是一种编程思想,常见的设计模式是对常见开发模式的总结归纳。它如同算法并不局限于某一种单一的编程语言,只要你对它理解透彻,那你在任何语言中都可以随心所欲的使用。

1995 年,GoF(Gang of Four,四人组/四人帮)合作出版了《设计模式:可复用面向对象软件的基础》一书,共收录了 23 种设计模式,从此树立了软件设计模式领域的里程碑,人称「GoF设计模式」。

「GoF设计模式」中将设计模式分成了三种创建型模式、结构型模式、行为型模式。

创建型模式,主要描述如何创建一个模型的模式,例如工厂模式,单例模式等等。

结构型模式,主要描述类和对象按照不同的模式组建成更大的结构,例如代理,桥接等等。

行为型模型,主要描述类和对象之间的共同协作互助完成单一对象无法完成的任务,例如观察者,迭代器等等。

一个合格的程序员应该熟练掌握二十三种设计模式,在你看到某个功能的时候应该不由自主的想到应该使用什么样的设计模式,当然如果现在你还不能很好的理解所有的设计模式,不要慌张,从现在开始了解它,熟悉它就可以,每个人都是从不了解到熟悉的。

这个专栏将会带你入门设计模式,并且尽可能让你掌握二十三种设计模式。

单例模式,也就是要确保在某一个类在全剧中只有一个实例,并且提供一个全局的访问点来访问这一实例。

如何实现一个单例模式,主要讲究两个字,一个字“存”,在类内存在一个属性,如果这个属性不为空那么就初始化当前类赋值给这一属性,另一个字“堵”,将所有的可能再次实例化当前类的方法都“堵”住,并且返回“存”的实例,当然在第一个次实例的时候需要正常实例化,也就是存放实例的属性为空时,可以正常实例化。

在前端开发中可以使用闭包实现单例模式(当然也可以使用 Class 语法糖或者构造函数形式实现),我们将设想一个案例:将一些配置内容或者一些其他内容放在一个单例中供其他内容访问。

首先我们将通过闭包实现一个配置信息的管理,首先我们写一个简单的闭包代码,使 config 属性不受外部污染。

1
2
3
4
5
6
const createConfig = (() {
let config;
return () => {
return config;
}
})()

但是我们要实现一个单例模式,就要在再次返回config前做判断,如果没有 config 要对他进行“实例化“并返回,如果存在 config 就将cunfig返回。

1
2
3
4
5
6
7
8
9
10
11
const createConfig = (() => {
let config;
return () => {
if (!config) {
config = { /* 创建单例对象的代码 */ };
}
return config;
}
})()

const config = createConfig(); // 初始化实例

当然这就完成了一个简单的配置对象的单例,但是我们发现现在的 createConfig 和直接返回一个对象没有什么区别,这是为什么?

有了配置对象的单例,不对对象进行操作基本上没什么用,属于”为醋包饺“,不提倡。

接下来我们将要实现的是,为配置对象新增 set 方法,这个方法将传入两个参数,分别是 keyvalue,作用就是将对象中的 key 的值改成 value 传入的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const createConfig = (() => {
let instance;
return () => {
// 如果不存在 instance 就初始化实例 否则返回原有的 instance
if (!instance) {
instance = {
config: {},
set ( key, value ) {
this.config[key] = value;
}
};
}
return instance;
}
})();

// 初始化两次 config 并且打印
const config1 = createConfig();
const config2 = createConfig();
console.log(
'config1:', config1.config,
'config2:', config2.config
);

// 修改 config 观察 config2 信息
config1.set('key', 0);
console.log('config2.config', config2.config);

当然在前端中不可能是一个页面打开一直不关闭,所以我们就要对数据进行缓存,一下案例中使用了 localStorageJSON 对数据进行了处理,只能处理简单数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const createConfig = (() => {
let instance;
return () => {
// 如果不存在 instance 就初始化
if (!instance) {
// 获取 localStorage 信息
const config = JSON.parse(localStorage.getItem('config') ?? '{}')
instance = {
config,
set ( key, value ) {
this.config[key] = value;
localStorage.setItem('config',
JSON.stringify(this.config ?? {})
);
}
};
}
return instance;
}
})();

// 初始化两次并打印
const config1 = createConfig();
const config2 = createConfig();
console.log(
'config1:', config1.config,
'config2:', config2.config
);

// 修改 config 观察 config2 信息
config1.set('key', 0);
console.log('config2.config', config2.config);

todo 使用 ES6 class形式 完成单例模式
todo 使用 构造函数形式 完成单例模式

昨天的面试中出现了一个题目:

将 对象 {6: 46, 8: 23, 14: 5, 10: 3} 按照值的顺序排列。

由于做开始他说的一个数组,说这个题的时候我以为是类数组对象,结果是是如上的对象,排序嘛,(不考虑时间复杂度和空间复杂度的话)基本上没什么难度, 但是一直有一个疑惑在脑中,Object 不是无序的吗?

本文主要探讨不同的 Object 定义方式(或者说声明方式)是否影响”Object 顺序“,”Object 顺序“是什么样的,以及常见处理”Object顺序“的方式。

首先再次之前我的认为是”对象是无序的,数组是有序的,如果处理对象的顺序的话,还是使用数组对象[{key, value}]的方式“ 。

ECMA-262_3rd_edition_december_1999 中有提到:

4.3.3 Object
An object is a member of the type Object. It is an unordered collection of properties each of which contains a primitive value, object, or function. A function stored in a property of an object is called a method.

也就是在 ES3 中是 Objiect 是乱序的,但是在 ES6 中逐渐开始在 Object 的中开始添加 Object 部分放的顺序之说。

不同浏览器在处理 for...in 时的解析顺序时不同的,在 Chrome 和 Opera 中遵循的是 ECMA-262 第五版本规范,而在 Firefox 和 Safari遵循的是 ECMA-262 第三版本规范。

在通常情况下,如果要处理对象的排序,我建议使用数组处理,也就是将对象处理为 [{ key, value }] 这样的的数组形式,按照数组排序,因为我还是觉得对象是无序的,尽管他是按照一定的顺序排序的,但为了避免在不同的浏览器中的排序不同还是将他作为数组处理比较好。

参考文献

  1. js能够保证object属性的输出顺序吗? - Jartto’s blog
  2. Does JavaScript guarantee object property order? - Stack Overflow
  3. ECMA-262_3rd_edition_december_1999

当前网页使用 Obsidian 作为编辑器以及知识库,Hexo 作为静态博客框架,也、就是将markdown文档转换为静态 html ,放在 Github 并且部署 Github Pages 上的。

个人需需要将 Obsidian 的 markdown 文档放置在一个私有仓库,将 Hexo Template 放置在共有仓库,github pages 也放在另一个仓库,当然 Hexo Template 可以和 Github Pages 放在相同的仓库中,如果Hexo Template也需要分离的话在将其分离。

静态博客框架仓库和部署仓库分离的好处是如果更换静态博客框架例如Gatsby,VuePress的话,可以不需要修改部署仓库只需要新建新的静态博客框架仓库,调整Obsidian 仓库的 Actions 就可以。

分离的坏处就是需要有一个额外的仓库管理静态博客框架,当然个人觉得好处大于坏处。

在 Obsidian 仓库,和 Hexo 仓库分别设置了两个 Actions,Obsidian 的仓库 Actions 是主 Actions,Hexo 的 Actions 只是为了触发主 Actions。

也可以将主 Actions 放在 Hexo 上,甚至应该放到静态博客框架仓库里面,如果要更换静态博客框架,肯定需要重新修改主 Actions ,而且是大幅修改,而 Obsidian Actions 只需要修改触发的主 Actions 就可以,减少了不必要的 Obsidian 仓库的变动,如果没有仓库洁癖这些问题都是小问题。

本网站的部署 Actions 整体思路大致如下( Local 代表本地仓库):

sequenceDiagram
participant Local as Local
participant Obsidian as Obsidian
participant Hexo as Hexo Template
participant Github as Github Pages
opt 推送 Obsidian 触发
    Local->>Obsidian:推送
    Obsidian->>Obsidian: 触发
end
opt 推送 Hexo template 触发
    Local->>Hexo: 推送
    par Hexo Actions
    Hexo->>Obsidian: 触发
    end
end
par Obsidian Actions
Hexo->>Obsidian: 拉取
Obsidian->>Obsidian:生成静态文件
Obsidian->>Github:推送
end

CSS优先级是一个值得思考的问题,再次之前我对CSS优先级的理解是:

!important>内联样式>ID选择器>类选择器>类型选择器

相信很多人对CSS优先级的理解也是这样的,但是一篇文章(CSS选择器的优先级(精讲版) (biancheng.net))上面书写了关于CSS 选择器优先级的计算规则的内容,使我开始对CSS优先级进行重新研究。

根据W3C给出关于选择器特异性(specificity,国内一般称优先级)的解释,选择器分为ABC三个等级,其中A为ID选择器,B包括类选择器、属性选择器和伪类,C包括类型选择器和伪元素,当然还存在一个通用选择器,但是通用选择器一般忽略。

等级包含选择器
A计算选择器中 ID 选择器的数量
B计算选择器中类选择器、属性选择器和伪类的数量
C计算选择器中类型选择器和伪元素的数量

优先级的计算,从A级开始到C级结束,如果到C级是两个选择器的优先级还是相等的那么有限选择靠后的选择器。

重复简单选择器

CSS选择器允许重复出现简单选择器,并且简单选择器的重复出现会增加优先级。

1
2
3
4
5
6
7
.class.class{
background-color: red;
}

.class{
background-color: green;
}

也就是说如上代码中第一个选择器重复出现了.class选择器,第二个选择器只出现了一个.class选择器,这两种写法都是正确的,并且第一个选择器.class.class的优先级大于第二个选择器.class,所以结果是背景颜色将呈现红色。

选择器优先级 (A, B, C)
.class.class(0, 2, 0)
.class(0, 1, 0)

在低版本CSS中可能简单重复选择器会被忽略,如在ie8中重复id或被忽略,在ie5中重复的class或被忽略。

拒绝IE,从我做起!

特殊选择器

一些伪类和其他选择器中存在一些特殊的选择器,因此单独定义了这些特殊选择器的特异性。

  1. 选择器:is():not():has()的优先级是选择器列表中最具有复杂性的选择器的优先级取代。
  2. 选择器:nth-child():nth-last-child()的优先级是伪类本身的优先级(计为一个伪类选择器,也就是计为B),再加上选择器列表中最具复杂性的选择器的优先级。
  3. 选择器:where()伪类的优先级被零代替,也就是没有优先级,再优先级计算中不做数。
  4. 通用选择符以及其他选择符在优先级中不计数。

优先级计算

选择器优先级 (A, B, C)
.class(0, 1, 0)
#Red(1, 0, 0)
.container :is(.container>#Red, .container>.class)(1, 2, 0)
.container #Red.class:nth-child(1)(1, 3, 0)
:is(.container>.class.class)(0, 3, 0)
#Red:is(.container>.class)(1, 2, 0)
.container div:nth-child(1)(0, 2, 1)
:is(#Red.class)(1, 1, 0)
#Red.class(1, 1, 0)
#Red.class:nth-child(1)(1, 2, 0)
#Red#Red(2, 0, 0)

代码片段

codepen

specificity求和

在一些其他文档中将讲A、B、C分别比作100,10,1 进行求和,是不准确的,如果按照这样做那么10个class是不是相当于一个id,显然不是。

CSS Level 1Selectors Level 3中也有这样的描述。

在主流浏览器中高等级高于低等级是即使ABC求和相同也不会优先使用后声明的CSS。

造成这样的原因是权重的进制是并不是十进制,CSS 权重进制在 IE6 为 256,后来扩大到了 65536,现代浏览器则采用更大的数量。也可以理解选择器的权值不能进位,或者理解为选择器权值ABC单独计算比较。

关于!important

MDN指出“使用 !important 是一个坏习惯,应该尽量避免”,并给出了使用!important 的情况:

  • 一定要优先考虑使用样式规则的优先级来解决问题而不是 !important
  • 只有在需要覆盖全站或外部 CSS 的特定页面中使用 !important
  • 永远不要在你的插件中使用 !important
  • 永远不要在全站范围的 CSS 代码中使用 !important

以及替代 !important的方法:

  1. 更好地利用 CSS 级联属性
  2. 使用更具体的规则。在您选择的元素之前,增加一个或多个其他元素,使选择器变得更加具体,并获得更高的优先级。
  3. 对于(2)的一种特殊情况,当您无其他要指定的内容时,请复制简单的选择器以增加特异性。

推荐阅读优先级 - CSS(层叠样式表) | MDN (mozilla.org),了解更多!important的使用意见。

其他 CSS 优先规则

CSS 优先规则1: 最近的祖先样式比其他祖先样式优先级高。

CSS 优先规则2: “直接样式”比”祖先样式”优先级高。

CSS 优先规则3: 优先级关系:内联样式 > ID 选择器 > 类选择器 = 属性选择器 = 伪类选择器 > 标签选择器 = 伪元素选择器。

CSS 优先规则4: 计算选择符中 ID 选择器的个数(a),计算选择符中类选择器、属性选择器以及伪类选择器的个数之和(b),计算选择符中标签选择器和伪元素选择器的个数之和(c)。按 a、b、c 的顺序依次比较大小,大的则优先级高,相等则比较下一个。若最后两个的选择符中 a、b、c 都相等,则按照”就近原则”来判断。

CSS 优先规则5: 属性后插有 !important 的属性拥有最高优先级。若同时插有 !important,则再利用规则 3、4 判断优先级。

注意: 文档树中元素的接近度(Proximity of elements)对优先级没有影响。

参考文献

  1. Selectors Level 4
  2. 优先级 - CSS(层叠样式表) | MDN (mozilla.org)
  3. CSS 样式优先级 | 菜鸟教程 (runoob.com)
0%