【D1n910】学习《JavaScript 设计模式》(1[1/2]/6)
正常操作,正常分析,大家好,我是D1n910。
本系列【D1n910】学习《JavaScript 设计模式》(1/6)正式开坑了!
这本书是我很久以前买的,现在在看了《你不知道的JavaScript》等系列以后,再看这些书,就感觉好了很多。思想层面上提高了一点嘛。
我本来想隆重介绍下作者的,但是看到作者现在在开培训班了,我个人之前参加过UI设计的培训班,结果不是很好,所以这边就不介绍了。
这些纷纷扰扰,我等不便参与,还是沉浸在学习的海洋里吧!

本期我们学的是 第一篇 面向对象编程,第 1 章 灵活的语言 —— JavaScript


github仓库地址
https://github.com/D1N910/JavaScript-design-patterns

第一篇 面向对象编程
第 1 章 灵活的语言 —— JavaScript
1.1、入职第一天
刚刚毕业的小新入职了大厂前端。

在厂里小新是用原生 JavaScript 开发代码的。
今天产品经理给小新分配了一个任务,让小新去做一个验证表单功能的任务。
小新是有2年大学打代码任务的老前端了好吧。
这个任务要验证用户名称,小新一看,啪,很简单啊。
马上用 JavaScript 闪电编写了下面的内容:
function checkName() {
// 检查用户名称
}
function checkEmail() {
// 检查用户邮箱
}
……
心满意足地提交了一个 pr (合并代码申请)到团队项目里。
这时候负责走读代码
(走读代码:看看代码有没有问题,没问题就合并代码,有问题就不合并代码并进行讨论沟通解决问题)
的阿蛋看了这个 PR。
“宝贝,这个有问题,你要改一改。你创建了好多个全局变量。要不得。”
“啊,这?这是变量,这不是函数吗?”
“函数难道不是变量吗?”
阿蛋的反问,让小新挠头了:“函数怎么会是变量呢?”
1.2、函数的另一种形式
阿蛋噼里啪啦地打了这些内容
var checkName = function () {
// 检查用户名称
}
var checkEmail = function () {
// 检查用户邮箱
}
……
“你看这样和你之前的效果是不是一样的。”
小新挠了挠头,“是一样的,但这样……”
“这样就要在用的时候提前说明, 但是这样就很明显地发现你创建了3个变量,保存了函数来实现你的功能,和之前你用 function 定义是一样的。所以说你也定义了3个全局变量。
这些用 function 定义的变量和 var 定义的全局变量一样,也是直接挂在到 window 对象下的”
阿蛋打开控制台给小新看了下面这张图。小新果然发现确实是挂在到 window 对象下了。

“那这样会有啥问题吗?”
“从功能上来说当然是没问题的。你是一个人开发的时候这么写没问题,但是我们是一个团队 ,你这么写可能会影响到别人,别人写了同样名称的变量也会覆盖你的功能。如果你写了大量的方法,那么这类的问题是比较难发现的。”
“那如何才能避免呢?”
“你可以把这些方法放到一个变量里保存,这样就减少了覆盖别人或者被覆盖的风险,而且一旦出现这个问题,也能更快地发现!”
1.3、用对象收编变量
对象,小新知道这个东西,就是 "{}" 嘛。
“是这么写吗”
小新写了下面的代码,把变量放到了 CheckObject 对象里。
var CheckObject = {
checkName: function () {
// 检查用户名称
},
checkEmail: function () {
// 检查用户邮箱
}
……
}
“好啊,妙啊,你还是蛮懂的,这样我们所有的函数变成了 CheckObject 对象下的方法了,用点语法就可以调用方法啦!比如检测姓名 CheckObject.checkName() 。只要在原来的调用方法的基础上,加上对象名称就可以了。”
1.4、对象的另一种形式
我们也可以用点语法来在对象里创建方法。不过要先定义 CheckObject 为一个对象,在 JavaScript 中,function 也是对象,所以可以这么做。
var CheckObject = function() {}
CheckObject.checkName = function () {
// 检查用户名称
}
新创建的方法使用方式和之前是一样的。但是这样不太方便,别人使用的时候不能够复制一份。
因为在咱们这边用的话,是希望能够拷贝来用的,但是目前这个对象类在new来复制的时候,不能够复制上刚刚点语法定义的函数。
就像这样

* 实际上这里感觉蛮怪的,想要做复制拷贝的话,可以直接用 `{}` ,对象拷贝下也能用,不过这里是为了引出类的复制,也 OK,下面就说了这种对象拷贝的。
1.5、真假对象
如果只是想简单复制,可以放到一个对象里返回
var checkObjectAnotherCopy = function () {
return {
checkObjectAnotherObjctCheckNameAnother: function () {
console.log('1.5 真假对象 function checkObjectAnotherObjctCheckNameAnother already running');
}
}
}
每次调用这个方法的时候,就会把新对象返回来的,所以明面上 checkObjectAnotherCopy,实际上用的都是新的对象,这样每个人在使用时就互不影响了。
// 返回新对象
// 使用时候不会相互影响
var a = checkObjectAnotherCopy();
a.checkObjectAnotherObjctCheckNameAnother();
1.6、类也可以
上面这种方式创建出来的对象和原来的对象checkObjectAnotherCopy没啥关系,我们希望创建真正的类。
var CheckObject = function () {
this.checkName = function () {
// 检查用户名称
},
this.checkEmail = function () {
// 检查用户邮箱
}
}
像上面这样写,就创建了一个构建函数,也可以看作是一个类,可以这个类来创建对象。记得要用 new 关键字。
var c = new CheckObject();
c.checkName();
c.checkEmail();
这样我们都可以对类实例话(用类创建对象),每个人都会有一套属于自己的方法。
1.7、节约资源,共用同一个方法
这里的类创建有一个小的问题,就是每次创建的时候,内部this上挂载的方法都要被重新定义一次,会造成一定的性能、资源消耗,可以把这些方法挂载到类的原型上。
var CheckObject = function () {}
checkObjectp.prototype.checkName=function(){
console.log('1.7一个检测类 function checkNameAnother already running');
}
// 也可以直接在 prototype 配置
checkObjectp.prototype = {
checkName1: function () {
console.log('1.7 一个检测类 function checkName1 already running');
},
checkName2: function () {
console.log('1.7 一个检测类 function checkName2 already running');
},
checkName3: function () {
console.log('1.7 一个检测类 function checkName3 already running');
}
}
这样的话,后继定义的对象都用的同一个方法了。
原因是因为这里存在 JavaScript 的原型链概念,用点语法访问JS对象的属性,如果直接找不到的话,会去到这个对象的原型 prototype 上去找。
这部分的内容,可以参考我下面写的这篇文章。

1.8、链式调用方法
我们使用过 JQuery,里面有好骚的操作,就是可以用点语法连续调用使用 ⬇️
$('#id').html('hello').animate(...)
这里的原理是因为每个方法的调用都会返回 JQuery 对象,就完成了这样的操作,在对象中的每个函数都会返回一个 this,也就是对象本身 ⬇️
var checkObjectCdy = {
checkName1 : function () {
console.log('1.8 方法还可以这样用 function checkName1 already running');
return this;//代表对象本身
},
checkName2 : function () {
console.log('1.8 方法还可以这样用 function checkName2 already running');
return this;
},
checkName3 : function () {
console.log('1.8 方法还可以这样用 function checkName3 already running');
return this;
}
}
checkObjectCdy.checkName1().checkName2().checkName3();
我们当然也可以用在之前学的类里来进行使用。
var checkObjectCdyP=function(){}
checkObjectCdyP.prototype={
checkName1 : function () {
console.log('1.8 方法还可以这样用 原型 function checkName1 already running');
return this;//代表对象本身
},
checkName2 : function () {
console.log('1.8 方法还可以这样用 原型 function checkName2 already running');
return this;
},
checkName3 : function () {
console.log('1.8 方法还可以这样用 原型 function checkName3 already running');
return this;
}
}
//使用的时候也要声明
var d = new checkObjectCdyP();
d.checkName1().checkName2().checkName3();
1.9、函数的祖先
要知道,函数本身是对象,它不是孤立的,它实际上是继承自 Function 的,所以我们可以通过修改 Function 的 prototype 的方式,让所有的函数拥有某个属性,像下面这样

这里我们可以用 instanceof 关键字验证确实上面的 c 是 Function 的实例,所以就能用 Function 上的原型方法。

再列举一种添加函数内自己的统一方法的案例
Function.prototype.addMethod=function(name,fn){
this[name]=fn;
}
var method = function(){};
method.addMethod('checkName',function(){
console.log('1.9 函数的祖先 抽象出一个统一添加方法的功能方法 function checkName already running');
})
method.checkName();
不过不推荐用这种方式,因为这回污染 Window 里的原生对象。
1.10、链式添加函数方法
使用上面的内容可以实现在给函数添加方法
//抽象出一个统一添加方法的功能方法
Function.prototype.addMethod=function(name,fn){
this.prototype[name]=fn;
return this;
}
var method = function(){};
method.addMethod('checkName',function(){
console.log('1.111 链式添加 function checkName already running');
return this;
});
method.checkName().addMethod('checkName2', function() {}).checkName2();
1.11 换一种方式使用方法
我们也可以使用类的形式来调用方法。像这样 ⬇️
var m = new method(); // 这个 method 类是 1.10 的内容
m.checkName();
End

通过本章节的学习,我们 Javascript 是一门非常灵活的语言,知道了函数是 Javascript 里的一等公民。
这里给两个课后练习:
(1)试着定义一个可以为函数添加多个方法的 addMethod 方法;
(2)试着定义一个既可以为函数原型添加方法又可以为其自身添加方法的 addMethod 方法