一、前言

惰性十足,这篇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模式(七),命名空间模式,私有成员与静态成员的更多相关文章

  1. js 独立命名空间,私有成员和静态成员

    独立的命名空间   1可以避免全局变量污染. 全局变量污染不是 说 被全局变量污染,而是说不会污染全局变量.   2实现私有成员. 在js中函数 就可以满足独立的命名空间的两点需求.   如:     ...

  2. JS OOP -04 JS中的公有成员,私有成员和静态成员

    JS中的公有成员,私有成员和静态成员 a.实现类的公有成员 b.实现类的私有成员 c.实现类的静态成员 a.实现类的公有成员 之前定义的任何类型成员都属于公有成员的范畴,该类的任何实例都对外公开这些属 ...

  3. 面向对象的JavaScript(3):私有成员和公开成员

    在小项目中对于JavaScript使用,只要写几个function就行了.但在大型项目中,尤其是在开发追求 良好的用户体验的网站中,如SNS,就会 用到大量的JavaScrpt,有时JavaScrip ...

  4. 结构型模式(七) 代理模式(Proxy)

    一.动机(Motivate) 在面向对象系统中,有些对象由于某种原因(比如对象创建的开销很大,或者某些操作需要安全控制,或者需要进程外的访问等),直接访问会给使用者.或者系统结构带来很多麻烦.如何在不 ...

  5. 行为型模式(七) 策略模式(Stragety)

    一.动机(Motivate) 在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂:而且有时候支持不使用的算法也是一个性能负担.如何在运行时 ...

  6. 初涉JavaScript模式 (11) : 模块模式

    引子 这篇算是对第9篇中内容的发散和补充,当时我只是把模块模式中的一些内容简单的归为函数篇中去,在北川的提醒下,我才发觉这是非常不严谨的,于是我把这些内容拎出来,这就是这篇的由来. 什么是模块模式 在 ...

  7. javascript模式(1)--私有成员

    javascript是基于对象的一门语言,没有想java等语言那样子拥有封装的特性.但是javascript可以通过闭包来进行模拟. 1.构造函数与私有成员 可以用构造函数形成一个闭包,实现内部成员的 ...

  8. JavaScript之命名空间模式 浅析

    来源于:http://www.cnblogs.com/syfwhu/p/4885628.html 前言 命名空间可以被认为是唯一标识符下代码的逻辑分组.为什么会出现命名空间这一概念呢?因为可用的单词数 ...

  9. JavaScript之命名空间模式

    前言 命名空间可以被认为是唯一标识符下代码的逻辑分组.为什么会出现命名空间这一概念呢?因为可用的单词数太少,并且不同的人写的程序不可能所有的变量都没有重名现象.在JavaScript中,命名空间可以帮 ...

随机推荐

  1. mac 清理

    1.iOS DeviceSupport   -- ~/Library/Developer/Xcode/iOS DeviceSupport 这个可重新生成!在连接旧设备调试时,会重新自动生成. 2.iP ...

  2. redis学习-集合set常用命令

    redis学习-集合set常用命令   1.sadd:添加一个元素到集合中(集合中的元素无序的并且唯一) 2.smembers:查看集合中所有的元素(上图事例) 3.srem:删除结合中指定的元素 4 ...

  3. [Java练习题] -- 1. 使用java打印杨辉三角

    package cn.fzm.demo1.array; import java.util.Scanner; /* * 需求:打印杨辉三角形(行数可以键盘录入) 1 1 1 1 2 1 1 3 3 1 ...

  4. python 中爬虫 content和text的区别

    一直在想requests的content和text属性的区别,从print 结果来看是没有任何区别 import requests headers = { "User-Agent" ...

  5. bzoj2730(割点+分类讨论)

    把割点删去后,剩下的联通块个数就是答案,方案数就是siz乘一起,但要讨论一些特殊情况,没有割点时答案直接算,一个联通块如果连接多个割点是不需算入答案的: #include<iostream> ...

  6. 如果解决小程序1024kb渲染之坑

    问题: 在小程序开发中如果有那么个场景和操作步骤,获取商品下拉列表商品列表data为goodsList 当从后台获取数据response.data.list,通常我们会setData({goodsLi ...

  7. noip第27课资料

  8. 11-Python操作excel

    1.python操作excel需要用到的库 python操作excel主要用到xlrd和xlwt这两个库,即xlrd是读excel,xlwt是写excel的库.可以直接pip安装这两个库,pip in ...

  9. redis启动出现错误creating server tcp listening socket 127.0.0.1:6379: bind No error

    creating server tcp listening socket 127.0.0.1:6379: bind No error 的解决方案如下按顺序输入如下命令就可以连接成功 1. redis- ...

  10. Python之旅Day5 列表生成式 生成器 迭代器 装饰器

    装饰器 器即函数,装饰即修饰,意指为其他函数添加新功能 装饰器定义:本质就是函数,功能是为其他函数添加新功能 装饰器涉及的知识点= 高阶函数+函数嵌套+闭包 在遵循下面两个原则的前提下为被装饰者新功能 ...