旋量群的大背景
虽然js中多于三种回到值,自上而下回到值和表达式回到值,而在合作开发情景下,将表达式曝露在自上而下回到值下的这时候,是两件十分脆弱的事,的的项目组协作合作开发的这时候,表达式的参数值被有意盗用,因此难于增容预测。这样的情形下,旋量群将表达式PCB在局部性的表达式回到值中,是一类十分最合适的作法,这种避免出现掉了被其它标识符阻碍的情形。
旋量群的采用
上面是一类最简单间接的旋量群实例
“`javascript
//爸爸既存
function mother(){
//公文包里的总回贴
let money = 100
//投资理财
return function (pay){
//回到余下回贴
return money – pay
}
}
//为女儿消费需求
let payForSon = mother()
//列印最终的余下回贴
console.log(payForSon(5))
“`
为的是易于认知,他们将内部表达式隐喻为爸爸既存,里头留存着总回贴这个表达式和消费需求这个行为,通过创建为女儿消费需求的这个行为对象,然后执行这个行为花费5元,回到余下的95元。
这个就是为的是将表达式money留存在mother既存内而避免曝露在内部的自上而下环境回到值中,只能通过mother()创建投资理财来影响money这个表达式。
由此可以归纳总结采用旋量群的三个步骤
1. 用外层表达式包裹表达式,表达式;
2. 外层表达式回到内层表达式;
3. 内部用表达式留存内部表达式回到的内层表达式
目的是为的是形成一个专属的表达式,只在专属的回到值中操作。
上述的旋量群标识符实例中,有一个缺陷的情景是,在后续不需要money表达式的情形下,没有释放该表达式,造成内存泄露。原因是payForSon这个表达式的回到值链引用着money对象,解决的办法是将payForSon = null就可以释放方法回到值,进而解除对money的引用,最终释放money表达式。
旋量群的扩展
表达式柯里化
在合作开发的情景中,有时需要通过旋量群来实现函数的柯里化调用。调用实例如下
“`
alert(add(1)(2)(3))
“`
这种连续的传参调用表达式,叫做表达式柯里化。
通过旋量群的实现方式如下
“`
function add(a){
//留存第一个参数
let sum = a
function tmp(b){
//从第二个表达式开始递加
sum = sum + b
//回到tmp,让后续可以继续传参执行
return tmp
}
tmp.toString = function(){
return sum
}
//回到加法表达式
return tmp
}
alert(add(1)(2)(3))
“`
上面他们来一步步预测,
1. add(1)执行时,留存第一个参数到sum表达式中,回到tmp表达式
2. add(1)(2)执行等于tmp(2),将2的值加到了表达式sum上,回到tmp表达式本身
3. add(1)(2)(3)执行等同于上述步骤的加到比表达式sum上,回到tmp表达式本身
4. alert(add(1)(2)(3))执行时,alert需要将值转为string显示,最终的tmp表达式执行tmp.toString,回到sum的值。
矩阵点击应用
该例子的demo标识符在我的github上,可以自行取阅
需求:在一个4*4的矩阵方块中,实现点击每个按钮时记录下各自的点击次数,相互之间互不阻碍。
思路:在按钮事件中采用旋量群,创建独立的存储表达式空间。
注意:下列的方案1到方案3是逐次演进的优化方案,需要按照方案标号的次序逐层认知,更有利于认知最终的优化方案
1.方案1
“`
…
let container = document.getElementById(container)
for (let r = 0; r < arr.length; r++) {
for (let c = 0; c < arr[r].length; c++) {
let cell = document.createElement(div)
cell.innerHTML = (${r},${c})
container.append(cell)
cell.onclick = (function () {
let n = 0
return function () {
n++
cell.innerHTML = 点${n}
}
})()
}
}
“`
在每个按钮上通过onclick绑定旋量群方法,存储操作独立的n表达式,这种就可以单独记录每个按钮的点击次数
。
2. 方案2
为的是改善方案1的缺点,他们引入内部数据arr来操作管控按钮点击数。
标识符实例如下:
“`
let arr = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
]
let container = document.getElementById(container)
for (let r = 0; r < arr.length; r++) {
for (let c = 0; c < arr[r].length; c++) {
let cell = document.createElement(div)
cell.innerHTML = (${r},${c})
container.append(cell)
cell.onclick = (function (r, c) {
return function () {
arr[r][c]++
cell.innerHTML = 点${arr[r][c]}
}
})(r, c)
}
}
“`
参照方案1 ,改动点包含两个
* 新增arr二维数组来记录点击数,这种可以达到与内部交互的目的
* onclick绑定的事件新增r,c两个参数,因此执行时传参进入,这种就可以把行列参数传递到方法内部(onclick的执行环境回到值与r,c所在的环境不一致,所以无法间接采用)
这种改进完以后,内部可以通过操作arr来与每个按钮的点击次数进行交互。
缺点:这种会将arr曝露在自上而下回到值下(可以在console控制台访问到),很容易被其它人或者模块误操作,也不利于PCB
3. 方案3
基于方案2的改进实现为,用一个立即执行的表达式包裹住整个执行标识符,这种就构建了一个表达式回到值来PCBarr表达式为私有。标识符如下:
“`
(function () {
let arr = [
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
]
let container = document.getElementById(container)
for (let r = 0; r < arr.length; r++) {
for (let c = 0; c < arr[r].length; c++) {
let cell = document.createElement(div)
cell.innerHTML = (${r},${c})
container.append(cell)
cell.onclick = (function (r, c) {
return function () {
arr[r][c]++
cell.innerHTML = 点${arr[r][c]}
}
})(r, c)
}
}
})()
“`
这种一个相对完整的按钮点击次数的方案就完成了。
采用call实现bind
这个需要有call和bind的采用知识的前提,可以自行百度哈
废话不多说,间接上标识符
“`
Function.prototype.bind = function(obj){
console.log(调用自定义bind表达式);
//留存当前表达式对象
let fun = this
//去除第一个obj参数,因此转换为js数组
let outerArg = Array.prototype.slice.call(arguments,1)
return function(){
//将arguments转为js数组
let innerArg = Array.prototype.slice.call(arguments)
//汇总所有参数
let totalArg = outerArg.concat(innerArg)
//调用内部留存的表达式,因此传参
fun.call(obj,…totalArg)
}
}
//调用实例
let zhangsan = {name:wawawa}
function total(s1,s2){
console.log(this.name + s1 + s2);
}
let bindTotal = total.bind(zhangsan,100)
bindTotal(200)
“`
重写表达式类的bind表达式,
1. 先将表达式对象(也就是上面实例中的total表达式)留存在fun表达式中,等于旋量群外层留存了fun,obj以及其它绑定的参数(虽然arguments是类数组对象,需要转换为数组,且去除第一个表达式obj);
2. 然后回到匿名表达式,在匿名表达式中,将内部和内部的参数进行转换和拼接;
3. 最终通过fun.call(obj,…totalArg),调用保存的表达式对象fun,因此通过call来实现传递绑定的回到值obj,和其它参数totalArg
注意:
* arguments是类数组对象,不能间接采用数组方法,需要转化为数组操作
* 外层表达式arguments转化时,需要剔除掉obj,因为上面的fun.call需要单独传递obj作为表达式回到值
* totalArg传递给call表达式时,需要通过…语法糖摊开数组