一小段js代码的思考(1)

今天随手写了一段代码,被@流浪大法师吐槽了,于是整理出了关于一个比较友善的写法以及在这段代码中的思考。
本篇有一点js trick在里面,也有模块化、函数科里化的一些写法。

先看下面的糟糕的第一段代码

1
2
3
4
5
6
var test  = (function(arg0){
console.log("inner-arg0="+arg0);
return function(arg0){
console.log("return-arg0="+arg0)
}
})();

这里我犯了一个错误,我习惯性地将函数最外层用括号包起来使其立即执行,这是声明式的用法,在这里运用错误。函数有两种形式,表达式和声明式,函数表达式本身可以立即执行。

第二段代码修改了一下

1
2
3
4
5
6
 var test2 = function(arg0){
console.log("inner-arg0="+arg0);
return function(arg0){
console.log("return-arg0="+arg0)
}
}();

去掉最外层括号之后本段代码也立即执行了。
若要让声明式立即执行,需要加一些小技巧。

最常见的写法

1
2
(function(){//这里的代码立即执行
})()

奇特的写法
赋值,逻辑,甚至是逗号,各种操作符都可以告诉解析器,这个不是函数声明,它是个函数表达式。

1
!function(){alert("aaa")}//可以使用+-~void new delete

在chrome中的执行结果如下:
exection code result show one
截图中可以看出,inner部分直接执行了,调用test()的时候return的匿名函数执行了。
因为之前写别的语言的缘故,习惯在函数的起始位置就传参,由于本段代码是立即执行的,所以第一次传参并没有起作用,后续的函数调用根本就没有调用到,这种写法真是太糟糕了。

以下是第三段代码

1
2
3
4
5
6
var test3 = function(arg0){
console.log("inner-arg0="+arg0);
return function(arg1){
console.log("return-arg1="+arg1)
}
};

在chrome中的执行结果如下:
exection code result show two
这里放弃了立即执行,采用需要时调用的方式。
第一次调用函数表达式,第二次调用返回的匿名函数,根据调用次序的不同,可以满足不同的业务需求。
第一次调用时,传参进行初始化,参数在函数作用域中声明,当函数执行完毕之后,被gc掉(属于标记清除)。
第二次调用使用的是返回的函数,这里是一个闭包,注意执行的时候要谨慎,不要轻易赋值给全局变量,内存泄漏你们都懂。

第四段代码是高阶函数的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//code4-1
var funcA = function(arg0){
//funcA do sth
}
function main(f,arguments){
//if need funcA
f.apply(this,arguments);
}
main(funcA,123);

//code4-2
var funcB = function(){
//funB do sth
return function(arguments-retB){
//funB return sth
}
}
function main(){
return function(f,arguments){
//main return
}
}
main()(funcB,123);

code4-1是标准的高阶函数调用,在需要的时候将函数作为参数传入,code4-2通过返回匿名函数的方式传参。在这里优势不明显,研究完继续填坑。

接下来看第五段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//moduleC
define([],function(){
var exports = {};
exports.func = function(arg0){
console.log("moduleC-arg0="+arg0)
};
return exports;
});

//module-main
require([moduleC],function(moduleC){
var exports = {};
exports.init = function(arg0){
console.log("transToC-arg0="+arg0)
};
exports.otherFunc(){
moduleC.func('module-main-arg1');
}
expots.init(555);
return exports;
})

这是commonjs的写法,这样写也为了函数解耦,个人觉得将函数绑定到对象的属性属于面向对象编程,和前面的说明式编程思想是不一样的。

1
2
3
4
5
> 批注:[2016-4-17修正]
> 今天吃饭的时候和WD大神讨论了这个问题,我这里的理解有误,更正一下。
> javascript本身没有类的概念,function A(){}作为“类”来使用的时候可以在原型链上添加方法,
> 但是只有当其new一个对象实例时,才算是这个function达到了“类”的作用;
> 只是将方法绑定在function A属性上,通过function A调用不能算是面相对象编程。

还有第六段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 //moduleA.js
var exports = function(){
return {
funcA1:function(arg0){
//do funcA1
}
funcA2:functioin(){
//do funcA2
}
}
}
module.exports = exports;

//moduleB.js
var A = require("./muduleA")();//pay attention
A.funcA1(123);
A.funcA2();

pay attention这种写法在nodejs中非常常见,这样写更符合事件触发机制,适用于将函数的执行作为参数传入,表示事件的传入,而事件发生时传参执行。
moduleA返回了函数对象,函数对象的属性的值是匿名函数,在moduleB中require了moduleA的返回函数,当需要使用moduleA的方法时,可以通过对象的键值调用。

思考
本文通过执行调用的顺序,结合以前写代码的习惯,讨论了javascript书写方式,若实践中得到更好的方式本文将持续修改。

附录
知乎上关于函数式编程的讨论