精读JavaScript模式(七),命名空间模式,私有成员与静态成员
一、前言
惰性十足,这篇2月19号就开始写了,拖到了现在,就是不愿意花时间把看过的东西整理一下,其它的任何事都比写博客要有吸引力,我要反省自己。
从这篇开始,是关于JS对象创建模式的探讨,JS语言简单直观,并没有模块,包,私有属性,静态成员等语法特性。而这一大章将介绍一些有用的模式,例如命名空间,依赖声明,模块模式以及沙箱模式等。这些能帮助我们更好的组织代码,减轻全局污染问题。
二、命名空间模式(Namespace Pattern)
命名空间可以减少全局变量的数量,还能有效避免命名冲突以及名称前缀的滥用,命名是个很头疼的事情,我想大家都有这种情况,命名到词穷。
JS默认语法是不支持命名空间,不过很好实现,命名空间很实用,对于类库,应用,插件编写,我们都可以为其创建一个全局对象,然后将所有的功能都添加到这个对象上,而不是到处申明大量的全局函数,对象等,这样看着很乱。
const MYAPP = {};
MYAPP.Parent = function () {};
MYAPP.number = 4;
MYAPP.modules = {};
MYAPP.modules.data = {};
上述代码中MYAPP就是命名空间对象,名称随意取,但是通常采用大写,还需要注意的是,一般大写的变量都表示常量。
很明显这样的写法在命名冲突上可能性就大大降低了,但是也存在一些问题,例如在MYAPP.modules.data这里代码量就明显增加了,整体会增加文件的大小,其次,在获取某个方法会属性时,得从MYAPP一层层往下读,读取较慢。而且该全局实例可能被误操作修改,虽然我们理论上说了这是常量不该被修改。
三、通用命名空间函数(思想是好的,但很鸡肋)
有个问题,当程序的复杂性提升我们很难保证命名空间的创建是否已存在,不小心修改了已存在的变量是很麻烦的事情,因此创建前的检查行为是更为安全的。
var MYAPP = MYAPP || {}
这个知识点后续内容我选择跳过了,原书中的观点是对于命名空间的创建最好做个检查,然后提供了一个通用的命名空间检测检查函数,我经过了测试,发现提供的函数永远返回一个空对象,并没达到预期的检查效果;其次,我认为创建一个对象每次都要检测真的过于繁琐,即便是封装一个检查函数,我还得调用。例如a.b.c.d.e,我不可能对于每层都做一个检测是否存在,安全创建思想是好的,但个人觉得过于鸡肋了。
四、对象的私有属性和方法
JS并没有专门提供保护私有成员,方法的语法,我们在全局创建一个对象,是可以轻易访问到对象的所有属性的。
let obj = {
num:1,
getNum:function () {
console.log(this.num);
}
};
//在函数外部可以轻易访问
console.log(obj.num);//
obj.getNum();//
即便是构造函数,也是如此。
// 构造函数
function GetNum() {
this.num = 2;
this.getNum = function () {
console.log(this.num);
}
}
let Num = new GetNum();
console.log(Num.num);//
Num.getNum();//
如何做到对象属性私有化,我们可以使用闭包做到这一点,只有闭包内部函数才可以访问到内部变量,外部无法直接访问。
function GetNum() {
let num = 3;
this.getNum = function () {
console.log(num);
}
}
let Num = new GetNum();
console.log(Num.num);//undefined
Num.getNum();//
除了调用getNum方法以外,我们并不能直接访问到num变量,所以一般我们称getNum方法为特权方法,因为它拥有访问num属性的特殊权限。
来聊聊特权方法权限的问题,假设我们的闭包返回的是一个对象,而非一个字符串。通过特权方法可以修改影响到闭包内部的本地变量。
function BoxInfo() {
let boxSize = {
widht:200,
height:300,
color:'yellow'
};
this.getBox = function () {
return boxSize;
}
}
//实例一个对象得到box1
let box1 = new BoxInfo(),
size = box1.getBox();
//我们修改size的颜色
size.color = 'bule';
//再取一次size信息,可以看到size颜色已被修改
size1 = box1.getBox();
console.log(size1)//{widht: 200, height: 300, color: "bule"}
//如果在修改后你想取到没修改的初始数据,你只能再次new一个实例
let box2 = new BoxInfo(),
size2 = box2.getBox();
console.log(size2);//{widht: 200, height: 300, color: "yellow"}
在取得实例修改颜色,后续再读取对象发现颜色已改变,这是肯定的,毕竟对象的赋值只是赋予了值的引用地址而非值本身,这种随意修改数据的做法不太安全,针对这个问题,我们可以使用“最低授权原则”,永远不要给出比需求更多的东西。
比如需求是要访问boxSize的height与color属性,那么特权方法不再是可以访问整个对象,而是只能访问到长与颜色属性,像这样:
this.getBox = function () {
return {
height:boxSize.height,
color:boxSize.color
}
}
我们只提供需求需要的属性,拼装为全新的对象返回,后续无论你怎么修改,我们永远得到的是最初的原始数据。
或者,当我们第一次得到box1实例时,深拷贝一份,作为原属性不再动用它,那另一份数据就随便你玩了。“最低授权原则”这个思想我觉得还是蛮不错的。
除了通过构造函数创建私有成员外,我们也可以通过对象字面量结合自调函数来达到目的。
(function () {
var name = "时间跳跃";
myobj = {
getName : function () {
return name;
}
}
})();
let myName = myobj.getName();
console.log(myName);//时间跳跃
这种实现方式就是通过自调函数创建了一个独立的作用域,外部无法访问,但是可以通过函数内部的对象访问到私有属性name,思想上是差不多的。
五、原型和私有成员(属性)
使用构造函数创建私有成员有个弊端,或者说使用构造函数创建实例时都会存在的弊端,每当调用一次构造函数,私有成员都会被创建一次。
这是因为每次new一个构造函数,都隐性的创建了一个空对象赋予给this,然后复制构造函数this上的属性方法,最终返回this,这点在精读JS模式三这篇文章的第四个知识点有说,有疑惑可以去看看。
同理,哪怕是在创造私有成员时,如果这个成员很多地方都会用到,那就没必要加载构造函数中被反复创建,直接将此成员添加在prototype上。
function Mine() {
let name = "echo";
this.getName = function () {
console.log(name);
};
};
//假设age属性每个实例都需要使用,就不要加在上方构造函数了,每次new都要创建,没必要
Mine.prototype = (function () {
var age = 26;
return {
getAge : function () {
console.log(age);
}
};
})();
let me = new Mine();
me.getName();//echo
me.getAge();//
六、静态成员(属性和方法)
1.构造函数的静态方法
当我们希望某个方法只有构造函数自身可以使用,实例无法继承使用,此方法就应该使用静态方法。
而在JS中并没有专门创建静态成员的语法,但我们可以通过构造函数添加属性的方法来添加静态方法。
let Func = function () {};
//这是func的静态方法
Func.myName = function () {
console.log('My name is echo');
};
//这是func的实例方法
Func.prototype.myAge = function () {
console.log('My age is 26');
};
Func.myName();
let me = new Func();
me.myAge();
在上述代码中,我为函数Func添加了一个静态方法myName和一个实例方法myAge。
myName方法之所以是静态方法是因为Func函数可以直接调用,它不需要指定一个对象去调用它,也不需要实例调用。但myAge方法则需要实例调用。当然相对的,函数Fcun无法直接调用实例方法,就像实例无法直接调用静态方法。
Func.myAge()//无法找到
me.myName()//无法找到
当然我们也可以将静态方法添加在原型链上,像这样(其实看到这里,我所理解的静态方法就是直接添加在函数上的方法,照常理说实例是无法使用的)
Func.prototype.myName = Func.myName
let me = new Func();
me.myName()//My name is echo
但区别在于,通过Func调用myName函数时,函数this指向Func函数,但通过后者实例调用时,this指向了实例me,这是有区别的。
2.构造函数的静态属性与私有静态属性
静态属性添加与静态方法相同,直接添加在构造函数上。
let Parent = function () {};
//静态方法
Parent.sayAge = function () {
console.log(this.age);
};
//静态属性
Parent.age = 26;
Parent.sayAge()//
什么是私有静态属性呢?有两大特点,第一,此属性在所有由同一构造函数创建的对象中可共享;第二,不允许在构造函数外部访问。
let KissMe = (function() {
let counter = 0;
return function() {
console.log(counter += 1);
};
})();
console.log(KissMe);
let one = new KissMe();//
let two = new KissMe();//
let three = new KissMe();//
上述代码中,我定义了一个记录亲吻我(实例)次数的构造函数,其中变量counter外部无法访问,且三次调用得到的实例共享counter,因为第二次调用时counter已经变成了1而非0,那么我们可以说counter就是一个私有的静态属性。
仔细看代码,其实就是一个构造函数被包裹在了一个自调函数中,去掉外层自调函数来看,这个实现的本质就是一个全局变量counter以及一个使用此变量的函数。
let counter = 0;
let KissMe = function() {
console.log((counter += 1));
};
console.log(KissMe);
let one = KissMe(); //
let two = KissMe(); //
let three = KissMe(); //
有没有发现,假设我们想知道一个构造函数被new了多少次,或者想知道这个实例是构造函数的第几个孩子,这个简单的实现就能计算出次数。(也许真的会用到)
上面自调函数的例子说是构造函数其实有点牵强,毕竟我们new KissMe的时候函数都已经执行完毕了,都没有通过实例调用方法的机会了,所以我们改改代码。结果还是一样,只是更像构造函数模式了。
另外,私有成员和私有静态成员的区别是,私有成员在每次实例中都是一个新的,并不会共享,很明显私有静态成员第二个实例受到了第一个实例调用时的影响。
let KissMe = (function() {
let counter = 0,
getNum = function() {
counter += 1;
};
getNum.prototype.getLastId = function() {
console.log(counter);
};
return getNum;
})(); let one = new KissMe();
one.getLastId();//
let two = new KissMe();
one.getLastId();//
let three = new KissMe();
one.getLastId();//
通过上面的例子我们可以看到,静态属性(公有或私有)可以包含和实例无关的方法或数据,创建实例时,这些私有属性不会被反复创建,但实例却可以使用,我感觉与原型链继承比较像,但与原型链添加方法的不同在于,最终执行时this指向不同,前面举例有说。
七、有趣的对象链式调用模式
假设我们需要连续调用一个对象上的多个方法,且操作的数据有所关联,我们就可以在每次调用时,直接将this作为函数调用的返回值,从而避免每次调用返回值作为下次调用函数参数的繁琐。
let obj = {
value: 1,
plus: function(a) {
this.value += a;
return this;
},
reduce: function() {
this.value -= 1;
return this;
},
multiply: function() {
this.value *= 2;
console.log(this.value);
}
};
obj.plus(3).reduce().multiply();//
这个挺像promise链式结构的写法,每次promise的执行都返回一个新的promise对象,这里就是每次返回了this,因为操作的全是this,这个就不多说了。
使用链式调用模式很明显能节约代码量,其实阅读起来更像一个句子,更容易将函数之间的调用关联起来。其实这种模式非常常见,比如我们常用的JQ获取DOM的写法:
document.getElementById("#echo").appendChild(new node);
那么到这里,第五章的内容大概就看完了,其实博客中我省略了比较多的东西,比如沙箱模式,模块模式等,在看之前我还是有所期待的,但在实际阅读中,这几个知识点的收货是极少的,一方面是例子难懂以及存在错误,其实可能我个人境界还不是太高,看了也无法立刻在实际开发中实践出来,所以更多是记录了一些我个人觉得有意义的东西,哪怕是多知道了一句概念。
睡觉吧,要收收心了。
精读JavaScript模式(七),命名空间模式,私有成员与静态成员的更多相关文章
- js 独立命名空间,私有成员和静态成员
独立的命名空间 1可以避免全局变量污染. 全局变量污染不是 说 被全局变量污染,而是说不会污染全局变量. 2实现私有成员. 在js中函数 就可以满足独立的命名空间的两点需求. 如: ...
- JS OOP -04 JS中的公有成员,私有成员和静态成员
JS中的公有成员,私有成员和静态成员 a.实现类的公有成员 b.实现类的私有成员 c.实现类的静态成员 a.实现类的公有成员 之前定义的任何类型成员都属于公有成员的范畴,该类的任何实例都对外公开这些属 ...
- 面向对象的JavaScript(3):私有成员和公开成员
在小项目中对于JavaScript使用,只要写几个function就行了.但在大型项目中,尤其是在开发追求 良好的用户体验的网站中,如SNS,就会 用到大量的JavaScrpt,有时JavaScrip ...
- 结构型模式(七) 代理模式(Proxy)
一.动机(Motivate) 在面向对象系统中,有些对象由于某种原因(比如对象创建的开销很大,或者某些操作需要安全控制,或者需要进程外的访问等),直接访问会给使用者.或者系统结构带来很多麻烦.如何在不 ...
- 行为型模式(七) 策略模式(Stragety)
一.动机(Motivate) 在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂:而且有时候支持不使用的算法也是一个性能负担.如何在运行时 ...
- 初涉JavaScript模式 (11) : 模块模式
引子 这篇算是对第9篇中内容的发散和补充,当时我只是把模块模式中的一些内容简单的归为函数篇中去,在北川的提醒下,我才发觉这是非常不严谨的,于是我把这些内容拎出来,这就是这篇的由来. 什么是模块模式 在 ...
- javascript模式(1)--私有成员
javascript是基于对象的一门语言,没有想java等语言那样子拥有封装的特性.但是javascript可以通过闭包来进行模拟. 1.构造函数与私有成员 可以用构造函数形成一个闭包,实现内部成员的 ...
- JavaScript之命名空间模式 浅析
来源于:http://www.cnblogs.com/syfwhu/p/4885628.html 前言 命名空间可以被认为是唯一标识符下代码的逻辑分组.为什么会出现命名空间这一概念呢?因为可用的单词数 ...
- JavaScript之命名空间模式
前言 命名空间可以被认为是唯一标识符下代码的逻辑分组.为什么会出现命名空间这一概念呢?因为可用的单词数太少,并且不同的人写的程序不可能所有的变量都没有重名现象.在JavaScript中,命名空间可以帮 ...
随机推荐
- Linux如何挂载U盘
1,以root用户登陆 先加载USB模块 modprobe usb-storage 用fdisk -l 看看U盘的设备 假如U盘是sda1 2,确定在 目录 /mnt 下建立了 文件夹 / ...
- 破解某普通话测试app会员
设备要求 已root的Android手机 软件要求 反编译工具 jeb.APK改之理(APK IDE) hook工具 frida.xposed. 布局分析工具 Android Device Monit ...
- excle记录
比较两列不一样的数据 https://jingyan.baidu.com/article/fd8044fa23eef05030137a66.html
- Python的条件判断语句------if/else语句
计算机之所以能做很多自动化的任务,因为它可以自己做条件判断. 比如,输入用户的年龄,根据年龄打印不同的内容... Python程序中,能让计算机自己作出判断的语句就是if语句: 例: age = 25 ...
- VS2015配置OpenCV,使用mfc摄像头程序测试
转自:https://blog.csdn.net/Lee_Dk/article/details/80466523 这只是介绍了如何加入OpenCV,怎么查找OpenCV请看出处. 新建一个项目.找到属 ...
- 现网环境业务不影响,但是tomcat启动一直有error日志,ERROR org.apache.catalina.startup.ContextConfig- Unable to process Jar entry [module-info.class] from Jar [jar:file:/home/iufs/apache-tomcat/webapps/iufs/WEB-INF/lib/asm
完整的错误日志信息: 2019-03-19 15:30:42,021 [main] INFO org.apache.catalina.core.StandardEngine- Starting Ser ...
- lombok学习
lombok的官方地址:https://projectlombok.org/ lombok的Github地址:https://github.com/rzwitserloot/lombok lombok ...
- java中接口和继承的区别
实际概念区别:区别1:不同的修饰符修饰(interface),(extends)区别2:在面向对象编程中可以有多继承!但是只支持接口的多继承,不支持'继承'的多继承哦而继承在java中具有单根性,子类 ...
- python csv读写
https://blog.csdn.net/taotiezhengfeng/article/details/75577998
- 【repost】js window对象属性和方法相关资料整理
window对象有以下方法: open close alert confirm prompt setTimeout clearTimeout setInterval clearInterval mov ...