JavaScript学习——函数
2015-07-25 本文已影响182人
ting723
1. 本文是在学习廖雪峰先生的JavaScrip教程 后的归纳
一、JavaScript函数
- JavaScript的函数是"头等公民",而且可以像变量一样使用,具有强大的抽象能力
- 借助抽象,可以不关心底层具体实现,而直接在最高层次上思考问题
- 函数是最基本的代码抽象的方式
二、函数的定义和调用
- 常规定义
function abs(x){
return x===0;
}
* `function` 关键字,指出函数的定义
* `abs`是函数的名称
* `(x)`括号列出函数的参数,多个参数以`,`分隔
* `{...}`之间的代码是函数体,可以包含若干语句,也可以没有任何语句
* 函数无论有没有`return`都会返回结果,无return只是结果为`undefined`
* 由于JavaScript的函数是一个对象,函数名abs为一个函数对象,函数名abs可以视为指向该函数的变量
- 匿名函数定义
var abs = function(x){ return x===0; };
- 此种情况下,
function(x){..}
为匿名函数,没有函数名,将该函数赋给变量abs,可以通过变量abs
就可以调用该函数 - 上述定义和常规完全等价,但需注意要在匿名函数末尾加上
;
,表示赋值语句结束
- 此种情况下,
- 函数的调用
- 调用函数时,按顺序传入参数即可
- 由于JavaScript允许传入任意个参数而不影响调用,因此,传入的参数比定义的参数多也没问题
- 传入参数比定义的少也没问题,不过,此时函数的参数将收到undefined,因此,为了避免收到undefine,需要进行类型检查
function abs(x){ if(typeof x!=='number'){ throw 'Not a number'; } return x; }
-
arguments
- Javascript 中还有一个关键字arguments,仅在函数内部起作用,并且永远指向当前函数的调用传入的所有参数,类似
Array
而非Array
- 利用
arguemnts
,可以获取调用者传入的所有参数,也就是,即使函数不定义任何参数,还可以拿到参数的值 - 小心
return语句
,由于JavaScript引擎有一个在行末自动添加分号
的机制
function foo(){ return //实际解析这样return ; {name:'foo'}; // {name:'foo'}; }
- 把arguments对象转换成一个真正的数组
var args = Array.prototype.slice.call(arguments);
- Javascript 中还有一个关键字arguments,仅在函数内部起作用,并且永远指向当前函数的调用传入的所有参数,类似
三、变量作用域
- 作用域
- var 声明的变量是有作用域的
- 变量在函数体内部声明,则该变量的作用域为整个函数体,在函数体外不可引用该变量
- 不同函数内部的同名变量相互独立,不受影响
- 由于JavaScript的函数可以嵌套,内部函数可以访问外部函数定义的变量,反过来,则不行
- JavaScript的函数在查找变量时从自身函数定义开始,从"内"向"外"查找,如果内部函数定义了与外部函数重名的变量,则内部函数的变量将"屏蔽"外部函数的变量
- 变量提升
- JavaScript 会先扫描整个函数体的语句,把所有声明的变量"提升"到函数顶部
- JavaScript 引擎自动提升了变量y的声明,但不会提升变量y的赋值
- 要严格遵守
在函数内部首先申明所有变量
这一规则,最常见的做法是用一个var
申明函数内部用的变量
function foo(){ var x = 1, y = x + 1, z, i; for(i=0;i<100;i++){ .... } }
- 全局作用域
- 不在任何函数内定义的变量就是具有全局作用域
- JavaScript默认有一个全局对象
window
,全局作用域实际上被绑定window
的一个属性'use strict' var course = 'JavaScript'; alert(course); alert(window.course);
- 直接访问全局变量
course
和访问window.course
是完全一样的 - 顶层函数的定义被视为一个全局变量,并绑定到
windows
对象 -
alert()函数
也是window的一个变量 - JavaScript实际上只有一个全局作用域,任何变量(函数也是变量),如果没有在当前函数作用域中找到,就会继续往上查找,最后在全局作用域中没有找到,则报ReferenceError错误
- 名字空间
- 全局变量会绑定到window上,不同的JavaScript文件如果使用了相同的变量,或者定义了相同名字的顶层函数,会造成命名冲突
- 减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中
- 把自己的代码全部放入唯一的名字中,会大大减少全局变量冲突的可能,许多著名的JavaScript库就是这样干的:如JQuery
- 局部作用域
-
for
循环等语句块中是无法定义具有局部作用域的变量 - 为了解决块级作用域,ES6引入了新的关键字
let
,用let
代替var
可以申明一个块级作用域的变量
-
- 常量
- 在ES6之前,无法申明一个常量,通常采用大写的变量来表示"这是一个常量,不要修改它的值"
- ES6标准引入了新的关键字
const
来定义常量,const
与let
都具有块级作用域
四、方法
-
this 关键字
- 在一个对象中绑定函数,称为这个对象的方法
- 示例:
var xiaoming={ name:'小明', birth:1990, age:function(){ var y = new Date().getFullYear(); return y - this.birth; } }; xiaoming.age;//function xiaoming.age() xiaoming.age();//返回小明的年纪
- 在一个方法内部,
this
是一个特殊变量,它始终指向当前对象 - 要保证
this
的正确调用,必须用obj.xxx()
的形式调用 - 在
strict模式
下让直接调用的函数中的this
指向undefined
- 在
strict模式
下,通常在函数没有指向正确的位置,那它将指向全局对象的window
- 在方法内部定义其他函数时,可以在方法内部加上
var that=this;
方便函数调用对象的属性
-
apply
- 在独立的函数调用中,根据是否是
strict
模式,this
指向undefined
或window
- 函数本身的
apply
方法,接受两个参数:- 需要绑定的
this
变量 -
Array
,表示函数本身的参数
- 需要绑定的
- 与
apply()
的类似方法是call()
,唯一区别是:-
apply()
把参数打包成Array在传入 -
cal()
把参数按顺序传入
-
- 对普通函数调用,通常把
this
绑定为null
- 在独立的函数调用中,根据是否是
- 装饰器
- 利用
apply()
,我们可以动态改变函数的行为 - JavaScript的所有对象都是动态的,即使内置的函数,也可以重新指向新的函数,如统计某内置函数的调研次数,可以用自定义函数替换掉默认的内置函数
var count = 0; var oldParseInt = parseInt; // 保存原函数 window.parseInt = function () { count += 1; return oldParseInt.apply(null, arguments); // 调用原函数 }; // 测试: parseInt('10'); parseInt('20'); parseInt('30'); count; // 3
- 利用
五、高阶函数
- 定义
- Higher-order function 高级函数
- JavaScript的函数其实都是指向某个变量
- 一个函数可以接收另一个函数作为参数,这种函数称之为高阶函数
- 编写高级函数就是让函数能够接收别的函数
- 高级函数有强大的抽象能力,可以使核心代码保持得非常简洁
function add(x,y,f){ return f(x)+f(y); }
- map/reduce
- 示例
function pow(x){ return x*x; } var arr = [1,2,3,4,5,6,7,8,9]; arr.map(pow);
-
map()
作为高阶函数,事实上是把运算规则抽象,不但可以计算简单的函数,也可以计算任意复杂的函数 - 如可以吧
Array
的所有数字转化为字符串:var arr=[1,2,]; arr.map(String);
- Array的
reduce()
把一个函数作用域Array
的[x1,x2,x3,..]
上,这个函数必须接收两个参数,reduce()
把结果继续和序列的下一个元素做累积计算
[x1,x2,x3,x4].reduce(f)=f(f(f(x1,x2),x3),x4);//reduce类似递归 var arr=[1,3,5,7,9]; arr.reduce(function(x,y){ return x+y; });
- 示例
- filter
- 也是一个常用的操作,它用于把
Array
的某些元素过滤掉,然后返回剩下的元素 - 和
map()
类似,Array
的filter()
也接收一个函数,和map()
不同的时,filter()
把传入的函数依次作用于每个元素,然后根据返回值是true
还是false
决定保留还是丢弃该元素
- 也是一个常用的操作,它用于把
- sort
- 排序算法: 比较的过程必须通过函数抽象出来,通常规定,对于两个元素
x
和y
,如果认为x<y
,则返回-1
,如果认为x===y
,则返回0
,如果认为x>y
,返回1
- 排序算法不用关心具体的比较过程,而根据比较结果直接排序
- JavaScript的
Array
的sort()
方法是用于排序,排序规则如下:- 字符串根据
ASCII
码进行排序(小写字母的ASCII码在大写字母之后) - 默认将所有的元素先转换成
String
在排序 - 可以接收一个比较函数来实现自定义排序
- 字符串根据
- 示例
var arr = [10, 20, 1, 2]; arr.sort(function (x, y) { if (x < y) { return 1; } if (x > y) { return -1; } return 0; }); // [20, 10, 2, 1]
- 排序算法: 比较的过程必须通过函数抽象出来,通常规定,对于两个元素
六、闭包
- 函数作为返回值
- 高级函数可以接收函数做参数,还可以把函数作为结果值返回
- 示例
function lazy_sum(arr){ var sum = function(){ return arr.reduce(function(x,y){ return x+y; } } } var f= lazy_sum([1,2,3,4,5]); f();
- 在上述例子中,函数
layz_sum
中定义了函数sum
,并且,内部函数sum
可以引用外部函数lazy_sum
的参数和局部变量,当lazy_sum
返回函数sum
时,相关参数和变量都保存在返回的函数中,这种称为"闭包(Closure)"的程序结构拥有巨大威力 - 当调用`lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数
var f1 = lazy_sum([1, 2, 3, 4, 5]); var f2 = lazy_sum([1, 2, 3, 4, 5]); f1 === f2; // false //`f1()和`f2()的调用结果互不影响
- 闭包作用
- 返回的函数在其定义内部引用了局部变量
arr
,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,函数并没有立刻执行,而是调用了f()
才行 - 返回函数不要引用任何循环变量,或者后续会发生变化的变
- 若一定引用循环变量: 方法是在再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定函数参数的值不变
function count() { var arr = []; for (var i=1; i<=3; i++) { arr.push((function (n) { return function () { return n * n; } })(i)); } return arr; } var results = count(); var f1 = results[0]; var f2 = results[1]; var f3 = results[2]; f1(); // 1 f2(); // 4 f3(); // 9
- 创建一个匿名函数并立刻执行可以这么写:
(function(x) {return x*x;})(3);
- JavaScript 借助闭包,可以封装一个私有变量,闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来
'use strict'; function create_counter(initial){ //私有变量 var x = initial||0; return { inc: function(){ x +=1; return x; } }; } var c1 = create_counter(); c1.inc(); // 1 c1.inc(); // 2 c1.inc(); // 3 var c2 = create_counter(10); c2.inc(); // 11 c2.inc(); // 12 c2.inc(); // 13
- 闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来
- 闭包还可以把多参数的函数变成单参数的函数
function make_pow(n) { return function (x) { return Math.pow(x, n); } } // 创建两个新函数: var pow2 = make_pow(2); var pow3 = make_pow(3); pow2(5); // 25 pow3(7); // 343
- 返回的函数在其定义内部引用了局部变量
七、generator
- 定义
- generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次
- generator 有
function*
定义,除了return
语句,还可以使用yield
返回多次
- 使用
- 示例
function* fib(max) { var t, a = 0, b = 1, n = 1; while (n < max) { yield a; t = a + b; a = b; b = t; n ++; } return a; }
- 直接调用generator和调用函数不一样,仅仅是创建了一个generator对象,还没有去执行它
- 调用generator对象有两种方法:
- 不断调用generator对象的
next()
方法
next()方法会执行时,每次遇到var f = fib(5); f.next(); // {value: 0, done: false} f.next(); // {value: 1, done: false} f.next(); // {value: 1, done: false} f.next(); // {value: 2, done: false} f.next(); // {value: 3, done: true}
yield x;
就返回一个对象{value:x,done:true/false}
,然后暂停,返回value
表示yield
的返回值,done
表示这个generator对象是否已经执行结束,如果done
为true
,则value
就是return
的返回值。- 直接用
for...of
循环迭达generator
对象,这种方式不需要自己判断done
for (var x of fib(5)) { console.log(x); // 依次输出0, 1, 1, 2, 3 }
- 不断调用generator对象的
- generator可以把异步回调代码变成"同步"代码