您的位置:首页 > Web前端 > JavaScript

JavaScript函数

2015-07-18 15:04 609 查看
在JavaScript里,函数即对象,程序可以随意操控它们。比如,JavaScript可以把函数赋值给变量,或者作为参数传递给其它函数。因为函数就是对象,所以可以给它们设置属性,甚至调用它们的方法。JavaScript的函数可以嵌套在其它函数中定义,这样它们就可以访问它们被定义时所处的作用域中的任何变量。这意味着JavaScript函数构成了一个闭包,它给JavaScript带来了非常强劲的编程能力。

1、函数定义

function printprops(o) { // 函数声明语句,输出o的每个属性的名称和值
for (var p in o)
console.log(p + “: ” + o[p] + “\n”);
}
var square = function(x) { // 函数定义表达式,求传入参数的平方
return x * x;
}
data.sort(function(a, b) { return a - b; }); // 函数作为参数
var tensquare = (function(x) { // 函数定义表达式定义后立即调用,传入参数为10
return x * x;
}(10));
function hypotenuse(a, b) { // 嵌套函数
function square(x) { return x * x; }
return Math.sqrt(square(a) + square(b));
}


2、函数调用

printprops({x: 1}); // 函数调用
var calculator = {
operand1: 1,
operand2: 2,
add: function() {
this.result = this.operand1 + this.operand2;
}
};
calculator.add(); // 方法调用
var o = new Object(); // 构造函数调用
f.call(o); // 间接调用,以对象o的方法来调用函数f()


3、函数参数

函数实参、形参个数不同时,省略的实参对应的形参都将是undefined,多出的实参会自动省略,但可以通过arguments取得。

当调用函数的时候传入的实参比函数声明时指定的形参个数少,剩下的形参都将设置为undefined值,如下例子:

function getPropertyNames(o, /* optional */ a) {
a = a || [];
for (var property in o) a.push(property);
return a;
}
var a = getPropertyNames(o); // 将o的属性存储到一个新数组中
getPropertyNames(p, a); // 将p的属性追加到数组a中


当调用函数的时候传入的实参个数超过函数定义时的形参个数时,没有办法直接获得未命名值的引用。参数对象解决了这个问题,在函数体内,标识符arguments是指向实参对象的引用,实参对象是一个类数组对象,也包含一个length属性,这样可以通过数字下标就能访问传入函数的实参值,而不用非要通过名字来得到实参。

function max(/* ... */) { // 不定实参函数,实参个数不能为零
var max = Number.NEGATIVE_INFINITY;
for (var i = 0; i < arguments.length; i++)
if (arguments[i] > max) max = arguments[i];
return max;
}


除了数组元素,实参对象还定义了callee和caller属性,前者指代当前正在执行的函数,后者指代调用当前正在执行的函数的函数,这个在匿名函数中非常有用。

var factorial = function(x) {
if (x <= 1) return 1;
return x * arguments.callee(x -1);
}


JavaScript方法的形参并未声明类型,在形参传入函数体之前也未做任何类型检查,可以采用语义化的单词来给函数参数命名,或者给参数补充注释,以此使代码自文档化。

当一个函数的参数过多时,记住参数顺序难免有点麻烦,可行的是把传入的实参写到一个对象中,通过名值对进行调用,如下例子将原始数组的length元素复制到目标数组:

function arraycopy(/* array */ from,
/* index */ from_start,
/* array */ to,
/* index */ to_start,
/* integer */ length) {
// ...
}
function easycopy(args) {
arraycopy(args.from,
args.from_start || 0,// 默认值
args.to,
args.to_start || 0
args.length);
}
var a = [1, 2, 3, 4], b = [];
easycopy({from: a, to: b, length: 4});


4、函数作为值

function square(x) { return x *x; }
var s = square; // s和square指代同一个函数
s(4);
var o= {square: function(x) { return x * x; }}; // 函数作为对象中的一个属性值
var y = o.square(16);


函数是一个特殊的对象,这就意味着函数也可以拥有属性,如下例子,计算阶乘,并将计算结果缓存至函数的属性中:

function factorial(n) {
if (isFinite(n) && n > 0 && n == Math.round(n)) {// 有限的正整数
if (!(n in factorial))
factorial
= n * factorial[n - 1];
return factorial
;
}
else return NaN;
}


5、函数作为命名空间

函数为什么可以作为命名空间?因为,不在任何函数内声明的变量是全局变量,在整个JavaScript程序中是可见的,而在函数中声明的变量只在整个函数内是可见的,在函数的外部是不可见的,这样在函数内定义的变量就不会污染到全局空间。

6、闭包

和其它大多数现代编程语言一样,JavaScript也采用词法作用域,也就是说,函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。为了实现这种词法作用域,JavaScript函数对象的内部状态不仅包含函数的代码逻辑,还必须引用当前的作用域链。函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为闭包。

从技术的角度讲,所有的JavaScript函数都是闭包,它们都是对象,它们都关联到作用域链。定义大多数函数时的作用域链在调用函数时依然有效,但这并不影响闭包,且看如下例子。

var scope = “global”;
function checkscope() {
var scope = “local”;
function f() { return scope; }
return f();
}
checkscope() // local


修改上面的代码如下,返回结果依然是local,这就是闭包的特性。

var scope = “global”;
function checkscope() {
var scope = “local”;
function f() { return scope; }
return f;
}
checkscope()() // local


7、函数的属性和方法

因为函数也是对象,所以它们也可以拥有属性和方法。

在函数体里,arguments.length表示传入函数的实参的个数,而函数本身的length属性则有着不同的含义,函数的length属性时只读属性,它代表函数实参的期望数量,即形参数量,可以通过arguments.callee.length获得。此外,每一个函数都包含一个prototype属性,这个属性是指向一个对象的引用,这个对象称做原型对象。

call()和apply()方法可以看做是某个对象的方法,通过调用方法的形式来间接调用函数,它们的第一个实参是要调用函数的母对象,它是调用上下文,在函数体内通过this来获得对它的引用。比如,以对象o的方法的形式调用函数f(),并传入两个参数,如下:

f.call(o, 1, 2);
f.apply(o, [1, 2]);


bind()方法的主要作用就是将函数绑定到某个对象,当在函数f()上调用bind()方法并传入一个对象o作为参数,这个方法将返回一个新的函数,调用新的函数将会把原始的函数f()当做o的方法来调用,传入新函数的任何实参都将传入原始函数。如下例子:

function f(y) { return this.x + y; }
var o = {x: 1};
var g = f.bind(o);
f(2) // 3


和所有的JavaScript对象一样,函数也有toString()方法,大多数返回函数的完整源码。

定义函数还可以使用Function()构造函数,下面两种方式等效(不同的是,构造函数所创建的函数并不是使用词法作用域,函数体代码的编译总是会在顶层函数执行,也就无法捕获局部作用域):

var f = new Function(“x”, “y”, “return x * y;”);
var f = function(x, y) { return x * y; }


所有的函数都是可调用对象,但并非所有的可调用对象都是函数。

8、函数式编程

使用数组方法reduce()和map()计算平均值和标准差:

var sum = function(x, y) { return x + y; }
var square = function(x, y) { return x * y; }
var data = [1, 1, 3, 5, 5];
var mean = data.reduce(sum) / data.length; // 平均值
var deviations = data.map(function(x) { return x - mean; });
var stddev = Math.sqrt(deviations.map(square).reduce(sum) / (data.length - 1)); // 标准差


操作函数的函数,接收参数作为参数,如下例子判断数值的奇偶性:

function not(f) {
return function() {
var result = f.apply(this, arguments);
return !result;
}
}
var even = function(x) { // 判断x是否为偶数
return x % 2 === 0;
}
var odd = not(even); // 新函数,所做的事情完全与even()相反,也就是判断是否为奇数
[1, 3, 5, 7, 9].every(odd); // true 每个元素都是奇数


JavaScript中,还有一种函数叫做不完全函数,即把一次完整的函数调用拆成多次函数调用,每次传入的实参都是完整实参的一部分,每个拆分开的函数叫做不完全函数,每次函数调用叫做不完全调用,这种函数变换的特点是每次调用都返回一个函数,直到得到最终运行结果为止。

在客户端JavaScript中,代码的执行时间复杂度往往成为瓶颈,一个可行的优化方法是使用缓存技巧,其实是牺牲了算法的空间复杂度以换取更优的时间复杂度,如下memory()函数,接收一个函数作为参数,并返回带有缓存功能的版本。

function memory(f) {
var cache = {};
return function() {
var key = arguments.length +
Array.prototype.join.call(arguments, “,”);
if (key in cache) return cache(key);
else return cache[key] =
f.apply(this, arguments);
};
}


写一个具有缓存功能的递归函数:

var factorial = memorize(function(n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
});
factorial(5) // 120,对于4到1的值也有缓存
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: