JavaScript权威指南(个人笔记):(四)函数
如果函数挂载在一个对象上,作为对象的一个属性,就称它为对象的方法。当通过这个对象来调用函数时,该对象就是本次调用的上下文(context),也就是该函数的this的值。
函数即对象。比如:可以把函数赋值给变量,或者作为参数传递给其他函数。因为函数就是对象,所以可以给它们设置属性,甚至调用它们的方法。
JavaScript函数可以嵌套在其他函数中定义,这样他们就可以访问他们被定义时所处的作用域中的任何变量。这意味着JavaScript函数构成一个闭包(closure)
(function() { }()); // 结束函数定义并立即调用他。注意:function之前的左圆括号是必须的,因为如果不写这个左圆括号,JavaScript解释器会试图将关键字function解析为函数声明语句。使用圆括号JavaScript解释器才会正确的将其解析为函数定义表达式。
函数的名称通常是动词!
如果函数里没有return语句,那么他就只执行函数体中的每条语句,并返回undefined给调用者。
函数的定义
两种方式:
1.函数声明语句。
一条函数声明语句,实际上是声明了一个变量,并把一个函数对象赋值给他。
例子:funciton foo() { } // 函数名foo是一个变量,变量指向函数对象
函数声明语句“被提前”到外部脚本或外部函数作用域的顶部, 所以可以被在他定义之前出现的代码所调用。
2.函数定义表达式。(可称为“函数直接量”)
注意:以表达式方式定义的函数,函数名称是可选的。定义函数表达式并没有声明一个变量。
函数可以命名,它需要一个名称来指代自己。如果一个函数定义表达式包含名称,函数的局部作用域将会包含一个绑定到函数对象的名称。实际上,函数的名称将成为函数内部的一个局部变量。
通常而言,以表达式方式定义函数时都不需要名称,这会让定义他们的代码更为紧凑。函数定义表达式特别适合用来定义那些只会用到一次的函数(比如下面展示的最后两个例子)
注意,以表达式来定义函数只适用于它作为一个大的表达式的一部分,比如在赋值和调用过程中定义函数,例子:
var square = function(x) { return x*x; } // 将表达式赋值给一个变量
var f = function fact(x) { if (x <= 1) retrun 1; else return x*fact(x-1); }; // 函数表达式可以包括名称,这在递归时很有用(虽然可以用变量调用函数,但函数的名称还是fact)
data.sort(function(a, b) { return a-b; }); // 函数表达式也可以作为参数传给其他函数
var tensquared = (function(x) {return x*x;}(10)); // 函数表达式有时定义后立即调用
注意:给变量赋值是不会提前的,因此,以表达式方式定义的函数在定义之前无法调用!
两种定义方式的区别:(只有函数名称区别,其他基本是没有区别了)
函数名称是函数声明语句必须的部分。它的用途就像变量的名字,新定义的函数对象会赋值给这个变量。
对函数定义表达式来说,这个名字是可选:如果存在,该名字只存在于函数体中,并指代该函数对象本身。
函数调用
4种方法:
1.作为函数
非严格模式下,调用上下文(this)是全局变量。严格模式下,调用上下文则是undefined。
以函数调用通常不用this关键字。不过,“this”可以用来判断当前是否是严格模式。
2.作为方法
如果有一个函数f和一个对象o,则可以用下面的代码给对象o定义一个名为m()的方法:
o.m = f;
给对象o定义了方法m(),如果需要两个参数,调用它时就像这样:
o.m(x, y);
上面的代码是一个调用表达式:它包括一个函数表达式o.m,以及两个实参表达式x和y,函数表达式本身就是一个属性访问表达式,这意味着该函数被当作一个方法,而不是一个普通函数来调用。
方法调用和函数调用有一个重要的区别,即:调用上下文(this)
属性访问表达式由两部分组成:一个对象(本例的对象o),和属性名称(m)。像这样,对象o即是调用上下文,可以用this引用该对象。
方法和this关键字是面向对象编程范例的核心。任何函数只要作为方法调用实际上都会传入一个隐式的实参,这个实参是一个对象,方法调用的母体就是这个对象。
方法链:当方法返回值是一个对象,这个对象还可以再调用它的方法。(当方法不需要返回值时,最好直接返回this)
需要注意的是,this是一个关键字,不是变量,也不是属性名。JavaScript不允许给this赋值。
很多人误以为调用嵌套函数时this指向调用外层函数的上下文。如果你想访问这个外部函数的this值,需要将this的值保存在一个变量里。通常用变量self来保存this。
3.作为构造函数
构造函数调用创建一个新的空对象,这个对象继承自构造函数的prototype属性
构造函数试图初始化这个新创建的对象,并将这个对象用做其调用上下文,因此构造函数可以使用this关键字来引用这个新创建的对象。
注意:尽管构造函数看起来像一个方法调用,它依然会使用这个新对象作为调用上下文。也就是说:
借用上面的例子,在表达式new o.m()中,调用上下文并不是对象o
4.间接调用
JavaScript中的函数也是对象,函数对象也可以包含方法。其中的两个方法call()和apply()可以用来间接的调用函数。
两个方法都允许显式指定调用所需的this值,也就是说,任何函数可以作为任何对象的方法来调用,哪怕这个函数不是那个对象的方法。
两个方法都可以指定调用的实参。
call()方法使用它自有的实参列表作为函数的实参。
apply()方法则要求以数组的形式传入参数。
可选形参
当传入的实参比形参少时,剩下的形参都将是undefine值。因此在调用函数时形参是否可选,是否可以省略,都应当保持较好的适应性。为了做到这一点,应当给省略的参数赋一个合理的默认值
例子:
function foo() {
if(a)
}
a = a || [ ] 如果a是真值的话就返回a,否则返回空对象。这是一个习惯的用法。
可变长的实参列表:实参对象(arguments)
arguments[]数组只定义在函数体中
当调用函数的时候,传入的实参个数超过函数定义时的形参个数时,没有办法直接获得未命名值的引用。实参对象解决了这个问题。
在函数体内,标识符arguments是指向实参对象的引用,实参对象是一个类数组对象。通过数字下标就能访问传入函数的实参值,而不是非要通过名字来得到实参:
假设函数f的形参只有一个x,如果调用时传入两个实参,第一个实参可以通过参数名x来获得,也可以通过arguments[0]来得到。第二个实参只能通过arguments[1]来得到。
length属性 传递给函数的参数个数(即实参个数)(比如,传入两个参数,arguments.length就是2)
callee属性指代当前正在执行的函数。有时非常有用,比如在匿名函数中通过callee来递归调用自身。
例子:
let foo = function (x) {
if (x <= 1) return 1;
return x * arguments.callee(x-1);
};
caller属性指代调用当前正在执行的函数的函数。通过这个属性,可以访问调用栈。
实参对象有一个重要的用处,就是让函数可以操作任意数量的实参。
将对象属性用做实参
当一个函数包含超过三个形参时,对于程序员来说,要记住调用函数中实参的正确顺序实在让人头疼。最好通过名值对的形式来转入参数,这样参数的顺序就无关紧要了。
做法:定义函数的时候,传入实参都写入一个单独的对象之中,在调用的时候传入一个对象,对象中的名值对是真正需要的实参数据。(例子178页)
实参类型
当一个方法可以接收任意数量的实参时,在参数的注释中,可以使用省略号:
function max(/* number... */) {}
有些情况下,应当添加类似的实参类型检查逻辑,因为宁愿程序在传入非法值时报错,也不愿非法值导致程序在执行时报错,相对而言,逻辑执行时的报错消息不甚清晰且更难处理。
作为值的函数
在JavaScript中,函数不仅是一种语法,也是值,也就是说,可以将函数赋值给变量,存储在对象的属性或者数组的元素中,作为参数传入另外一个函数等。
例子:
function square(x) { return x*x } // 创建一个新的函数对象,并将其赋值给变量square。
函数还可以赋值给其他的变量,并且仍可以正常工作:
let s = square; // 现在s和square指代同一个函数
square(4); // =>16
s(4); // =>16
除了可以将函数赋值给变量,同样可以将函数赋值给对象的属性:
let o = {square: function(x) { return x*x }}; // 对象直接量
let y = o.square(16); // y 等于 256
函数甚至不需要带名字,当把他们赋值给数组元素时:
let a = [function(x) { return x*x }, 20]; // 数组直接量
a[0](a[1]); // => 400 这句代码虽然看起来很奇怪,但的确是合法的函数调用表达式
作为命名空间的函数
为了防止变量污染,可以直接定义一个匿名函数,并在单个表达式中调用他:
(function() { // 匿名函数的表达式
// 模块代码
}()); // 结束函数定义并立即调用他
这种定义匿名函数并立即在单个表达式中调用他的写发非常常见,已经成为一种惯用法了。注意上面的圆括号的用法,function之前的左圆括号是必需的,因为入如果不写这个圆括号,JavaScript解释器会试图将关键字function解析为函数声明语句。使用圆括号JavaScript解释器才会正确地将其解析为函数定义表达式。使用圆括号是习惯用法,尽管有些时候没有必要也不应当省略。这里定义的函数会立即调用。
自定义函数属性
函数可以拥有属性。当函数需要一个“静态”变量来在调用时保持某个值不变,最方便的方式就是给函数定义属性,而不是定义全局变量,显然定义全局变量会让命名空间变得更加杂乱无章。
比如,假设你想写一个返回一个唯一整数的函数,不管在哪里调用函数都会返回这个整数。而函数不能两次返回同一个值,为了做到这一点,函数必须能够跟踪它每次返回的值,而且这些值的信息需要在不同的函数调用过程中持久化。可以将这些信息存放到全局变量中,但这并不是必须的,因为这个信息仅仅是函数本身用到的。最后将这个信息保存到函数对象的一个属性中,下面这个例子就实现了这样一个函数,每次调用函数都会返回一个唯一的整数:
foo.num = 1;
function foo() {
foo.num++;
}
foo();
foo.num; // => 2
闭包
函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。
所有JavaScript函数都是闭包:他们都是对象,他们都关联到作用域链
函数的执行用到了作用域链,这个作用域链是函数定义的时候创建的!和在哪里调用是没有关系的!这个特性可以捕捉倒局部变量(和参数),并一直保存下来,看起来像这些变量绑定到了在其中定义他们的外部函数。
词法作用域的规则:函数定义时的作用域链到函数执行时依然有效。
例子(1):
let scope = 'global scope'; // 全局变量
function foo() {
let scope = 'local scope'; // 局部变量
function f() { return scope; } // 在作用域中返回这个值
return f();
}
foo() // => local scope
例子(2):
let scope = 'global scope'; // 全局变量
function foo() {
let scope = 'local scope'; // 局部变量
function f() { return scope; } // 在作用域中返回这个值
return f;
}
foo()(); // => local scope
闭包可以捕捉到单个函数调用的局部变量,并将这些局部变量用做私有状态:
let uniqueInteger = (function () {
let counter = 0;
return function () { return counter++ };
}());
粗略来看,第一行代码看起来像将函数赋值给一个变量uniqueInteger,实际上,这段代码定义了一个立即执行的函数,因此是这个函数的返回值赋值给了变量uniqueInteger。
现在,我们来看函数体,这个函数返回另一个函数,这是一个嵌套函数,我们将它赋值给变量uniqueInteger,嵌套的函数是可以访问作用域内的变量,而且可以访问外部函数中定义的counter变量。
当外部函数返回之后,其他任何代码都无法访问counter变量,只有内部的函数才能访问到它。
length属性
定义函数时的形参个数
prototype属性
每一个函数都包含一个prototype属性,这个属性是指向一个对象的引用,这个对象称做“原型对象”(prototype object)。
每一个函数都包含不同的原型对象。
当将函数用做构造函数的时候,新创建的对象会从原型对象上继承属性。
call()和apply()方法
我们可以将call()和apply()看作是某个对象的方法,通过调用方法的形式来间接调用函数。
第一个实参是要调用函数的母对象,他是调用上下文,在函数体内通过this来获得对他的引用。
对于call()来说,第一个调用上下文实参之后的所有实参就是要传入待调用函数的值。
apply()有点类似,但传入实参的形式和call()有所不同,他的实参都放入一个数组当中。
注意:传入apply()的参数数组可以是类数组对象也可以是真实的数组。实际上,可以将当前函数的arguments数组直接传入(另一个函数的)apply()来调用另一个函数
例子:
let o = { x:1 };
function foo() { return this.x; }
foo.call(o); //=>1
foo.apply(o); //=>1
和下面代码的功能类似(假设对象o中预先不存在名为m的属性)
o.m = foo; // 将f存储为o的临时方法
o.m(); // 调用方法
delete o.m; // 将临时方法删除
bind()方法
将函数绑定至某个对象,返回一个新的函数。传入新函数的任何实参都将传入原始函数
例子:
function f(y) { return this.x + y } // 这个是待绑定的函数
let o = { x:1 }; // 将要绑定的对象
let g = f.bind(o); // 通过调用g()来调用o.f()
g(2); //=> 3
其他:除了第一个实参之外,传入bind()的实参也会绑定至this。
Function()构造函数
函数还可以通过Function()构造函数来定义
例子:
let f = new Function( "x", "y", "return x*y" );
等价于下面的代码:
let f = function(x, y) { return x*y };
注意:Function()构造函数并不需要通过传入实参以指定函数名。就像函数直接量一样,Function()构造函数创建一个匿名函数。
浙公网安备 33010602011771号