JavaScript-8函数
概况
- 形参实参
- this
- 对象的方法
- 构造函数
- 函数即对象
- 闭包
- 函数可以嵌套在其他函数中定义,这样他们就可以访问他们被定义时所处的作用域中的任何变量,这意味着Javascript函数构成了一个闭包
8.1函数定义
- 两种方式
- 函数定义表达式
- 函数声明语句
- 函数定义的必须部分
- 函数名称标识符
- 一对圆括号。包含由0个或者多个用逗号隔开的标识符组成的列表,这些标识符是函数的参数名称,他们就像函数体中的局部变量一样
- 一对花括号(包含0或多条JavaScript语句,这些语句构成了函数体)
1 | var square = function(x){ return x*x ;} |
- 函数声明语句的例子
1 | function printprops(o){ |
箭头函数
为什么叫Arrow Function?因为它的定义用的就是一个箭头
1 | x=>x*x; |
- 箭头函数相当于匿名函数,有两种格式
- 只包含一个表达式, 省略了{…}和return,就像上面
- 包含多条语句,这时不能省略
- 如果参数不是一个,就需要用括号()括起来
1 | //一个参数 |
如果要返回一个对象,就要注意,如果是单表达式,这么写会报错:
- x=>{foo:x}
因为和函数体的{…}有语法冲突,所以要改为:
- x=>({foo:x})
- 箭头函数和匿名函数有个明显的区别:箭头函数内部的this是词法作用域,由上下文确定
1 | var obj={ |
8.2 函数调用
- 调用JavaScript函数的四种方式
- 作为函数
- 作为方法
- 作为构造函数
- 通过他们的call()和aply()方法间接调用
函数调用
1 | printprops({x:1}); |
方法调用
- 方法调用和汉时调用的重要区别:
- 调用上下文
- 方法调用通过属性访问表达式
- 属性访问表达式由两部分组成
- 一个对象(o): 调用上下文,函数体可以用this引用
- 属性名称(m)
- 方法是个保存在一个对象的属性里的JavaScript函数
1 | o.m = f; |
给对象o定义了方法m(),调用时候她就像这样
1 | o.m(); |
- 例子
1 | var calculator = { |
方法链
- 当方法的返回值是一个对象,这个对象还可以再调用他的方法。形成链,每次调用的结果都是另外一个表达式的组成部分
1 | customer.surname.toUpperCase(); //调用customer.surname的方法 |
- 如果在设计的API中一直采用这种方式(每个方法都返回this)就可以进行链式调用
1 | shape.setX(100).setY(100).setSize(50).setOutline("red").setFill("blue").draw(); |
注意
不要把方法的链式调用和构造函数的链式调用混为一谈
方法和this关键词是JS面向对象编程的核心
方法调用实际上都会传入一个隐式实参(一个对象),方法调用的母体就是这个对象
```javascript
rect.setSize(width,height);
setRectSize(rect,width,height);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
+ 我们假设这两行代码的功能完全一样,他们都作用于一个假定的对象rect。可以看出第一行的方法调用语法非常清晰的表明这个函数执行的载体是rect对象,函数中的所有操作都将基于这个对象
+ this是一个关键字,不是变量也不是属性名,不允许给this赋值
```javascript
var o ={
m:function(){
var self = this; //讲this的值保存到一个变量中去
console.log(this===0); //输出true,this就是这个对象o
f(); //调用辅助函数f()
function f(){
console.log(this===o);//false:this的值是全局对象或者undefined
console.log(self===o);//true:self指外部函数的this值,也就是o
}
}
}
1 | function getAge(){ |
这样是不对的!!!
这样就对了
创建对象时构造
- 创建过程
- 首先创建一个新的空对象,然后传入指定的参数并将这个新对象当作this的值来调用与i个指定的函数。这个函数使用this来初始化新创建对象的属性
- 构造函数一般不会返回值,这个新创建并被初始化后的对象就是整个对象创建表达式的值
- 如果构造函数确实会返回一个对象值,则此时对象作为整个对象创建表达式的值,新创建的对象就废弃了
- 如果构造函数使用return单没有指定返回值,或者返回一个原始值,这时将忽略返回值,而使用这个新对象作为调用结果
间接调用
8.3函数的实参和形参
存在的问题
- 函数定义未指定函数形参的类型
- 函数调用没有对传入的实参值做类型检查
- 不检查传入形参的个数
可选形参
- 若传入的实参比函数声明式指定的形参个数少,剩下的形参都将是只给undefined值
- 应该给省略的参数赋一个合理的默认值
- 注意
- 用这种可选形参来实现函数时,需要将可选形参放在参数列表的最后,否则必须传入null或者undefined作为第一个形参
- 在函数定义中使用注释/optional/来强调形参式可选的
- 用这种可选形参来实现函数时,需要将可选形参放在参数列表的最后,否则必须传入null或者undefined作为第一个形参
可变长的实参列表:实参对象
- 若传入的实参个数超过函数定义时的形参个数,使用参数对象解决
函数体内,标识符arguments式指向实参对象的引用
- 通过数字下表就能访问传入函数的实参值
- 省略的实参都是undefined,多出的参数自动省略
- arguments并不是真正的数组,他是一个实参对象(它是一个对象,只是碰巧具有以数字为索引的属性),实参对象式一个类数组对象
- 在非严格模式下,当一个函数包含若干形参,实参对象的数组元素是函数形参所对应实参的别名,并且形参名称可以认为是相同变量的不同命名,单严格模式下不行
剩余参数
剩余参数语法允许我们将一个补丁数量的参数表示为一个数组
- 语法
- 例子
1 | function sum(...Args){ |
剩余参数和arguments对象的区别
- 剩余参数只包含哪些没有对应形参的实参,arguments对象包含了传给函数的所有实参
- arguments对象不是一个真正的数组,而剩余函数是真正的array实例,也就是你能够在它上面直接使用所有的数组方法,比如sort,map,forEach和pop()
将对象属性用作实参
- 通过名/值 对的形式传入参数,是参数顺序无关紧要
- 例子
实参类型
- JavaScript方法的形参并未声明类型,在形参传入函数体之前也没有做类型检查
- 应当在程序中添加实参类型检查逻辑
默认参数值
- 函数默认参数允许在没有值或undefined被传入时使用默认形参
1 | function multiply(a,b=1){ |
8.4作为值的函数
1 | function square(x){ return x*x ;} |
赋值给对象的属性
1 | var o = {square: function(x){return x*x ;}} //对象直接量 |
赋值给数组元素
1 | var a = [function(x){return x*x ;},20]; //作为数组的一个成员 |
作为参数传入另一个函数
1 | function add(x,y){ return x+y ;} |
自定义函数属性
- 函数可以拥有属性
1 | //初始化函数对象的计数器属性 |
- 例子
1 | //计算阶乘,并将结果缓存到函数的属性中 |
8.5 作为命名空间的函数
- 不在任何函数内声明的变量是全局变量,在整个JavaScript程序中都是可见的
- 我们常常就简单的定义一个函数用作临时的命名空间,在这个命名空间内定义的变量都不会污染到全局命名空间
1 | function mymodule(){ |
1 | (function(){ |
8.6 闭包
- 闭包:函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数的作用域内
1 | var scope ="global scope"; |
1 | var scope ="global scope"; |
- 闭包可以捕捉到局部变量(和参数),并一直保存下来,看起来想这些变量绑定到了在其中定义他们的外部函数
1 | var uniqueInteger = (function(){//定义函数并立即调用 |
- 例子
1 | function counter(){ |
1 | function counter(n){ |
1 | function addPrivateProperty(o,name,predicate){ |
- 例子
1 | //这个函数返回一个总是返回v的函数 |
- 例子
1 | //返回一个 函数 组成的数组,它们的返回值是0-9 |
- 注意
- 每个函数调用都包含一个this值,闭包在外部函数里是无法访问this的
- 除非外部函数将this转存为一个变量
- 每个函数调用都包含一个this值,闭包在外部函数里是无法访问this的
1 | var self = this;//将this保存至一个变量中,以便嵌套的函数能够访问它 |
- 闭包具有自己所绑定的arguments,因此闭包内无法直接访问外部函数的参数数组
- 除非外部函数将参数数组保存到另外一个变量中:
1 | var outerArguments = arguments; //保存起来以便嵌套的函数能使用它 |
8.7函数属性、方法和构造函数
- 函数也是对象,他们也可以拥有属性和方法
- 函数属性和方法
- Function()构造函数
length属性
- arguments.length 表示传入函数的实参的个数
- 函数本身的length属性值得是形参的数量
1 | function check(args){ |
1 | function foo (a,b,...rest){ |
prototype属性,在第九章进一步讨论
call() 和 apply()
- call()和apply()的第一个实参都变为this的值,即使传入的实参是原始值或者null或者undefined
- call(),第一个参数之后的所有实参是要传入带调用函数的值;
1 | f.call(o,1,2); |
- apply()实参都放到一个数组
1 | f.apply(o,[1,2]); |
例子
1 | function getAge(){ |
bind()方法
- 作用是把函数绑定到某个对象
1 | function f(y){return this.x+y;} //这是个待绑定的函数 |
- 除了第一个实参之外,传入bind()的实参也会绑定到this
toString()方法
Function()构造函数
- FUnction()构造函数可以传入任意数量的字符串实参,最后一个实参所表示的文本就是函数体
- 除了最后一个实参,其他实参字符串是指定函数的形参名字的字符串
1 | var f = new Function("x","y","return x*y"); |
- 需要特别注意
- 在运行时动态的创建并编译函数
- 每次调用Function()构造函数都会解析函数体,并创建新的函数对象
- 所创建的函数代码的编译总是会在顶层函数(全局作用域)执行
8.8函数式编程
- 在JavaScript中可以像操控对象一样操控函数,也就是说可以在Javascript中应用函数式编程技术、
使用函数处理数组
1 | //首先定义两个简单的函数 |
高阶函数
- 操作函数的函数,它姐手一个或者多个函数作为参数,并返回一个新函数
1 | //这个高阶函数会返回一个新的函数,这个新函数将他的实参传入f() |
1 | //所返回的函数的参数应当是一个实参数组,并对每个数组元素执行函数f() |
1 | //返回一个新的可以计算f(g(...))的函数 |
不完全函数
- 这个函数可以接受一些参数,这些参数中有一些参数可以被绑定成其他函数,然后返回一个新的函数,这个新的函数接收剩下的未绑定的的参数
记忆
- 将上次的计算结果缓存起来,在函数式编程当中,这种缓存技巧叫做记忆
1 | //返回f()的带有记忆功能的版本 |