JS函数的主要特点
JS的函数与Java迥异。这里把它的特殊之处归纳整理一下:
1)JS函数既是函数,又是对象,还相当于函数指针/委托。
2)JS函数可以被new,这时它变成了对象构造函数。
3)作为对象方法的JS函数是对象的属性,可以用名称文本作关键字访问,也可以被替换掉。这点类似函数指针/委托。
4)JS函数可以与其定义文本相互转化。可以打印出函数的完整定义,也可以把定义文本转成函数。这个是动态语言的优点。
5)JS函数支持嵌套。且内嵌函数可以通过变量返回被外部访问。
6)JS支持内嵌函数访问上一层作用域的变量,使得这种变量不被销毁,变成类似C语言中以static声明的静态局部变量。这个叫JS闭包。
JS函数的几种形式
我惊讶地发现,没有一种形式与Java相同。连箭头函数都不同。箭头函数倒是与C#相似。
简单形式的全局函数
JAVA/C#是静态语言,声明函数都是这样:
// 返回值类型 函数名称 (形参){函数体}
void func(String[] arguments) {
...
}
JS由于是动态语言,故省去了形参和返回值的类型描述,但是要用function关键字:
// function关键字 函数名称 (无须声明类型的形参){函数体}
function myFunction(a, b) {
return a * b; //
}
与其它语言一样,JS支持默认参数:
function myFunction(x, y = 10) {
// y is 10 if not passed or undefined
return x + y;
}
console.log(myFunction(0, 2)); // 输出 2
console.log(myFunction(5)); // 输出 15, y 参数的默认值
参数个数可变的全局函数
C#用params关键字指示可变参数,JAVA用...指示可变参数。JavaScript呢?什么都不用指示,直接用arguments对象访问。arguments对象是函数内自动产生的。例如:
function func() {
for(let i=0; i<arguments.length; i++)
console.log(`第${i}个参数=`, arguments[i]);
}
func();// 无输出
func(1); // 输出1行:第0个参数= 1
func(1,2);// 输出2行:第0个参数= 1;第1个参数= 2
func(1,2,3);// 输出3行
其实任何函数都相当于是可变参数的函数。声明几个形参,就从arguments中取前几个传过去:
function func(a, b) {
console.log(a+b, arguments.length);
}
func();// 输出NaN 0,因为a,b都是undefined
func(1);// 输出NaN 1,因为b是undefined
func(1,1);// 输出2 2
func(1,1,1);// 输出2 3
声明的形参叫显示参数。其它叫隐式参数。
用Function() 构造函数定义的函数
//Function的最后一个参数是函数体,前面的参数都是形参
var myFunction = new Function("a", "b", "return a * b");
var x = myFunction(4, 3);
这个是动态语言的优点,可以把字符串化成代码。
匿名函数
函数可以作为一个对象被赋值给一个变量:
var func = function abc() {
console.log("函数赋值给变量");
};
func();//非常像函数指针/委托
进一步,把函数名称省了,就产生了匿名函数。例如:
var func = (function () {
console.log("匿名函数");
});
func();
自调用函数
更进一步,对于仅在一处调用的函数,不但连函数名称省了,连赋值给变量也省了,声明函数的时候就把它调用了:
(function () {
console.log("我将调用自己!");
})();
(function (a, b) {
console.log(a + b);
})(1, 2);
在js前台打包形成的单文件中,其主体框架就是一个自调用函数。
箭头函数
在C#和Java中称为lamda表达式。形式与C#相同。ES5标准不支持,ES6标准增加。例如:
var x=3, y=4;
const ff = (x, y) => x * y;
console.log(ff(5,5));// 输出25
形式上等价于一个匿名函数:
var x=3, y=4;
const ff = function(x, y) {
return x * y;
}
console.log(ff(5,5));
作为对象方法的函数
这样的函数不是全局函数,是对象的一个属性值。例如:
var obj = {
a:1,
b:2,
func: function () {
return this.a + this.b;
}
}
console.log(obj.func()); // 输出3
console.log(obj.func); // 输出 [Function: func]
console.log(obj.func.toString()); /* 输出3行:
function () {
return this.a + this.b;
}
*/
相当于把函数赋值给一个变量,类似函数指针/委托。
作为对象构造函数的函数
es5及前的标准没有用class定义法的创新之举?权宜之计?反正es6标准迫于形式加上了。
常常会在各种node.js包的CJS模块中看到这么用。例如:
function Student(name, score) {
this.name = name;
this.score = score;
return this.name;
}
var x = new Student("John", 3);
console.log(x.name, "=", x.score); // 输出John = 3
console.log(Student("Rose", 5)); // 输出Rose
Student在这里既作为对象构造函数使用,也作为普通的全局函数使用。非常混乱!匪夷所思。那么Student指的是什么?对象还是函数,还是构造函数?你要log打印的话,是普通函数,不信可以用console.log(Student.toString()) 试试。
再加上this的混乱,让这个问题更加复杂。
对于下面的代码:
this.op='b';
console.log(this === globalThis);
function Student(name, score) {
this.name = name;
this.score = score;
console.log(this.op);
return name;
}
var x = new Student("John", 3);
console.log(x.name, "=", x.score);
console.log(Student("Rose", 5));
console.log(this.op);
Chrome的输出结果是:
Node.JS的输出结果是:
一个函数,它是作为对象构造函数还是普通的全局函数?主要看是不是在前面加了关键字new。
搞一个不伦不类的对象构造函数是难以理解的。怎么看似乎都没必要,直接用{}定义对象不行吗?例如:
function Person(name, age) {
this.name = name;
this.age = age;
}
var v = new Person("John", 50);
console.log(v);
var v2={name:"John", age:50};
console.log(v2);
你会看到v和v2的定义是一样的,原型链是一样的。既然如此,干嘛不用后面这种正常的对象定义式呢?不但如此,ECMAScript标准还创造了多种对象构造方式,百花齐放。
JS嵌套函数
函数内可以嵌套函数。初看难以理解,细想C#/JAVA中的委托、匿名函数不是也可以声明在一个函数内部吗,所以没什么奇怪的。例如:
function mylog() {
var func = function() {
console.log("abc");
}
func();
}
mylog();
变成下面这种形式,从C#/JAVA的视角看起来就匪夷所思了,但是它是可以工作的:
function mylog() {
function func() {
console.log("abc");
}
func();
}
mylog();
JS闭包
嵌套函数可以访问上一层作用域的变量。例如:
function add() {
var counter = 0;
function plus() {counter += 1;}
plus();
return counter;
}
var v = add();// v=1
除了访问上一层作用域的变量这一点,上面的代码换一种形式就比较容易理解了:
function add() {
var counter = 0;
var func = function plus() {counter += 1;}
func();
return counter;
}
var v = add();// v=1
内嵌函数可以赋值给变量,从而可以被外部访问。例如:
var add = (function () {
var counter = 0;
return function () {
console.log(counter);
return counter += 1;
}
})();
console.log(add.toString());
add();
add();
add();
这段代码能把人绕晕,输出结果是:
重点是那个匿名函数中的变量counter居然一直存在着,它相当于C语言中以static声明的静态局部变量。由于内嵌函数可以被外部访问,
JS函数与原型的关系
其它
虽然全局函数/变量是全局对象的属性,但是由于它们默认不可枚举,所以无法按属性访问,也无法修改。例如:
function func(){};
console.log(this["func"]);//输出undefined
多次用同一个名称定义函数,会导致问题。下面的代码将输出2然后输出3,而不仅仅是输出3:
function myFunction(arg1, arg2) {
this.firstName = arg1;
this.lastName = arg2;
}
var x = new myFunction("John","Doe");
function myFunction(a, b) {
console.log(arguments.length);
}
myFunction(1, 2, 3);
在对象的方法中,this 表示该方法所属的对象。在函数中,this 表示全局对象,但在严格模式下是undefined。
js中的this是个复杂而混乱的问题,在浏览器和node.js中的表现不完全相同!