《JS权威指南》学习笔记(六):函数

定义函数的两种方式

一是函数声明,实际上是声明了一个变量,该变量指向函数对象;
二是函数表达式,采用该方式函数的名称是可选的,通常没有函数名,但也可以有,例如:

var f = function fact(x) {
    if (x <= 1) {
        return 1;
    }
    else {
        return x * fact(x-1);
    }
}

此时,函数名称实际上是函数内部的一个局部变量。通常都不许要名称,除非像这样需要递归的时候。

使用函数表达式适合用来定义那些只会用到一次的函数。

函数声明并不是正真的语句,可以出现在全局或嵌套在其他函数中,但不能出现在if、循环、try…块中,但函数定义表达式可以出现在JS代码的任何地方。

函数命名

驼峰式,私有函数通常以_开头;
框架编程中可为常用函数指定短名称,例如$ == jQuery.

函数调用

作为:

  • 函数
  • 方法
  • 构造函数
  • 通过call、apply间接调用

方法调用

  1. 其调用的上下文是对象,在作为方法调用时实际上传入了一个隐式的实参,即对象。函数体内可使用this引用该对象。

  2. 当方法不需要返回值的时候最好直接返回this,这样就可以进行“链式调用”风格的编程。例如jQuery。

  3. this是关键字不是变量,不能给this进行赋值。

  4. 嵌套的函数不会从调用它的函数中继承this。如果嵌套的函数作为方法调用,this指向调用它的对象;如果作为函数调用,this不是全局对象(非严格模式)就是Undefined(严格模式)。因此,如果想要访问外部函数的this值,需将this值保存在一个变量里,这个变量和内部函数都在同一个作用域内。

间接调用

call和apply允许显示地指定调用所需的this值,使得任何函数都可以作为任何对象的方法来调用。前者是将参数挨个传入,后者是参数以一个数组“打包传入”,因此当传入一个函数的实参是数组,但形参却期待是单一个的,可以采用如下形式:

func.apply(this,arrOfArg);

函数的实参和形参

  1. 未传入的实参其值实际上为undefined,通常用 || 来设置可选形参的默认值:
function getPropertyNames(o, a) {
    a = a || []; //需注意使用||a必须预先声明,否则报错;此处形参已有则无需声明
    for(var pro in o) {
        a.push(pro);
    }
    return a;
}

2.当传入的实参个数超过了形参,没有办法直接获得未命名值得引用,但函数体内,有一个类数组对象arguments指向实参对象,可以通过下标来访问传入的实参。

function f(x) {
    console.log(x);
    arguments[0] = null;
    console.log(x);
}
var a = 12;
f(12);
var b = [1,2,3];
f(b);

上面代码中不管是对于实参a还是b,第二条console.log都显示null。执行arguments[0] === x 也是返回true。因此,不管实参是引用类型还是原始类型的值,arguments和x指代同一值,修改其中一个会影响到另一个。而不是像书上说的传入普通数组就不会输出null。

3.arguments对象的属性:

  • length
  • callee:指代当前正在执行的函数,在匿名函数中通过callee来递归地调用自身比较有用:
var fact = function(x) {
    if (x <= 1) {
        return 1;
    }
    return x * arguments.callee(x-1);
};
  • caller:指代调用当前正在执行的函数的函数

4.*为了在传入实参时简省记住形参顺序等的麻烦,可以将实参作为名/值对儿的形式传入,多个实参作为一个对象。*

自定义函数属性

函数是一种特殊的对象,可以像普通变量那样被赋值、作为参数、作为返回值。也可以有属性,当函数需要一个“静态“变量来在调用时保持某个值不变,可以定义为函数的属性而不是定义一个全局变量:

function fact(n) {
    if (isFinite(n) && n>0 && n==Math.round(n)) {
        if(!(n in fact)) {
            fact[n] = n * fact(n-1); //保存了每次的计算结果
        }
        return fact[n];
    }
    else {
        return NaN;
    }
}
fact[1] = 1;
fact(10);
for(var v in fact) {
    console.log(v + ': ' + fact[v]);
}

作为命名空间的函数

在函数内部定义的变量都是局部变量,因此可以定义一个函数来作为命名空间,内部定义的变量不会污染到外部的全局变量。

闭包

1.  明白作用域链:每当进入一个执行环境(全局或函数),都会生成一个变量对象用来保存该环境中的变量,这个对象会被添加到作用域链的最前面。内部的函数其作用域链中会包含指向外部环境作用域链上的变量对象的引用。
因此当外部函数执行完返回后,由于内部函数还持有对外部函数的变量对象的引用,那么外部函数作用域链上的变量对象就不会被销毁,所以如果外部函数执行返回了内部函数,通过内部函数仍然是能够访问到外部函数定义的变量的。
因此通过闭包可以间接地在函数外部访问到其内部定义的私有变量。
例子:

var uniqueInteger = (function() {
    var counter = 0;
    return function() { 
        console.log(counter);
        counter++; 
    };
})();
uniqueInteger(); //0
uniqueInteger(); //1
uniqueInteger(); //2

counter是函数定义的局部变量,但这个匿名函数立即执行并返回了一个闭包,这个闭包的作用域链中的变量对象持有外部函数的变量,因此当返回了闭包并赋值给uniqueInteger后仍然能在外部访问到counter。由于uniqueInteger持有对闭包的引用,因此闭包的作用域链一直都不会被销毁。

2.  函数在每次调用时都会创建一个新的作用域链。如果每次都能想明白形成作用域链上的变量对象到底是什么,就不难理解闭包了:
两个互不打扰的计数器:

function counter() {
    var n = 0;
    return {
        count: function() { return n++;},
        reset: function() { n = 0; }
    };
}
var c = counter();
var d = counter();

由于每次调用counter都生成了新的作用域链,因此c、d能够访问到各自不同的私有变量n。

3.   多个闭包可以共享包含它们的函数内部的私有变量。闭包可以通过外部函数返回或者作为外部对象的方法,这样就可以实现在外部也能调用闭包。

4.   一个函数内部的闭包是共享这个函数内定义的局部变量的,而不会说每个闭包都各自复制一份局部变量,注意每个闭包的作用域链上指向外部函数的变量对象都是同一个,即位于外部函数作用域链最前面的变量对象。

function constfuncs() {
    var funcs = [];
    for (var i = 0; i < 10; i++) {
        funcs[i] = function() {
            return i;
        };
    }
    return funcs;
}
funcs[5](); //9

这里,funcs存储了10个闭包的数组,所有的闭包都共享这一个局部变量i,而经过一轮循环后i变成了9.

5.   每个函数都有自己的this,因此嵌套函数是访问不到外部函数this值的,除非显示地存储在一个变量里。arguments对象也和this类似,必须存储在变量中才能被嵌套的函数访问到。

函数的属性、方法和构造函数

1.   arguments.length是实际传入的实参的个数,而arguments.callee.length即函数的length属性表示期待传入的实参的个数即形参的个数。

2.   prototype属性,每个函数都有一个该属性,它指向原型对象。当函数作为构造函数时,新创建的对象会从原型对象上继承属性。

3.   call和apply可以使得某个函数看起来像一个对象的方法,可以修改这个函数的调用上下文为传入的对象。在函数提内通过this获得传入的对象的引用。
在ES5严格模式中,传入的第一个实参都会变为this的值,即便传入的是原始值或null、undefined。而在ES3和非严格模式中,传入的null、undefined会被全局对象替代,原始值则会被包装对象所替代。

4.   bind方法可以将函数绑定要某个对象,使得它像这个对象的方法,bind返回一个新的函数,而不像call、apply那样修改作用域后立即调用。
实现bind方法:

function bind(f, o) {
    if (f.bind) {
        return f.bind(o);
    }
    return function() {
        return f.apply(o, arguments);
    }
}
function func(y) {
    return this.x + y;
}
var o = {
    x: 1
};
var resfunc = bind(func, o);
var res = resfunc(2); //3

传入bind的除第一个实参以外的其他参数会被绑定到this,该函数式编程技术被称为“currying”。即f.bind(o, …)可以传入一些参数按顺序作为f的参数,且传入的参数会作为o(即this)的属性。此外,f.bind(o,…)返回的函数在调用的时候还可以再传入实参,按照形参出现的顺序一一对应后面剩下的形参。
例如:

function f(y, z) {
    return this.x + y + z;
}
var g = f.bind({x: 1}, 2); //x==1, y==2
g(3); // z==3,返回6

5.   toString方法会返回函数的源代码。而Object.prototype.toString.apply(f)则返回”[Object Function]“。因为不同类型值的toString方法被重写了,因此检测类型的时候要用原型上的方法。

函数式编程

使用数组的map、reduce等方法可以进行函数式编程。

高阶函数

即接收一个或多个函数作为参数,并返回一个新的函数。是操作函数的函数。
例如 计算和的平法:

function compose(f, g) {
    return function() {
        return f.call(this, g.apply(this, arguments));
    }
}

var square = function(x) { return x * x; };
var sum = function(x, y) { return x + y; };
var squareOfSum = compose(square, sum);
squareOfSum(2, 3); //25

该函数接收两个函数f,g,并返回一个新的函数h,在h中进行f(g)的计算。h接收的参数传递给g,g的结果作为参数传递给f。g、f使用的this和h的相同。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值