JavaScript学习笔记-(函数)
函数
1.函数的定义和调用
1. (x)括号内列出函数的参数,多个参数以,分隔
1.
function myAbs(x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}
2.
var hisFunc = function(x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}
2. 函数的调用
JavaScript允许传入任意个参数而不影响调用,因此传入的参数比定义的参数多也没有问题,传入的参数比定义的少也没有问题,多的话只会按顺序使用参数
console.log(hisFunc(1,2,3));//只会调用第一个参数
console.log(hisFunc());
3. arguments
JavaScript还有一个免费赠送的关键字arguments,它只在函数内部起作用,并且永远指向当前函数的调用者传入的所有参数。arguments类似Array但它不是一个Array,实际上arguments最常用于判断传入参数的个数
function funcOne(x) {
console.log(x);// 10
for (var i=0; i<arguments.length; i++) {
console.log(arguments[i]); // a, b, c
}
}
funcOne('a','b' ,'c');
3. rest参数
//貌似没有支持的版本
由于JavaScript函数允许接收任意个参数,于是我们就不得不用arguments来获取所有参数
function aFunc(a, b) {
var i, restArry = [];
if (arguments.length > 2) {
for (i = 2; i<arguments.length; i++) {
restArry.push(arguments[i]);
}
}
console.log('a = ' + a);
console.log('b = ' + b);
console.log(restArry);
}
aFunc(1,2,3,4,5,6,7,8);
为了获取除了已定义参数a、b之外的参数,我们不得不用arguments,并且循环要从索引2开始以便排除前两个参数ES6引入了rest参数,上面的函数就可以写成
function aFunc(a, b, ...moreValues) {
console.log('a = ' + a);
console.log('b = ' + b);
console.log(moreValues);
}
aFunc(1,2,3,4,5,6,7,8);
2.变量作用域
1. 如果一个变量在函数体内部申明,则该变量的作用域为整个函数体,在函数体外不可引用该变量
2. 不同函数内部的同名变量互相独立,互不影响
3. 内部函数可以访问外部函数定义的变量,反过来则不行
4. JavaScript的函数在查找变量时从自身函数定义开始,从“内”向“外”查找。如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量
1.变量提升
下面并不报错 但是x中y值的部分为undefined
'use strict';
function foo() {
var x = 'Hello, ' + y;
alert(x);
var y = 'Bob';
}
foo();
2.全局作用域
不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性:
var aProperty = "guess";
console.log(aProperty);
console.log(window.aProperty);//被绑定到windowaProperty属性
以变量命名的函数也是window的一个属性 这样我们就可以想到`alert()`也是一个属性,我们就可以将这个函数实现给替换掉
3.名字空间
全局变量会绑定到window上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现
减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。例如:
// 唯一的全局变量MYAPP:
var MYAPP = {};
// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函数:
MYAPP.foo = function () {
return 'foo';
};
4.局部作用域
function stillCanUse() {
for (var i=0; i<100; i++) {
//
}
i += 10; // 仍然可以引用变量i
console.log(i);
}
stillCanUse(1);
为了解决这个问题ES6引入了新的关键字let,用let替代var可以申明一个块级作用域的变量
function foo() {
var sum = 0;
for (let i=0; i<100; i++) {
sum += i;
}
// i += 1; // 报错
}
5.常量
由于var和let申明的是变量,如果要申明一个常量,在ES6之前是不行的,我们通常用全部大写的变量来表示“这是一个常量,不要修改它的值”:
var PI = 3.141592654;
ES6标准引入了新的关键字const来定义常量,const与let都具有块级作用域:
const PI = 3.14;
PI = 3; // 某些浏览器不报错,但是无效果!
PI; // 3.14
3.方法
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var y = new Date().getFullYear();
return y - this.birth;
}
};
xiaoming.age(); // 今年
console.log(xiaoming.age());//26
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 26, 正常结果
getAge(); // NaN 这是this指向的问题
1.apply
想指定函数的this指向哪个对象,可以用函数本身的apply方法,它接收两个参数,第一个参数就是需要绑定的this变量,第二个参数是Array,表示函数本身的参数
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 26
getAge.apply(xiaoming, []); // 26, this指向xiaoming, 参数为空
另一个与apply()类似的方法是call(),唯一区别是:
apply()把参数打包成Array再传入
call()把参数按顺序传入
对普通函数调用,我们通常把this绑定为null
2.装饰器
利用apply(),我们还可以动态改变函数的行为。
JavaScript的所有对象都是动态的,即使内置的函数,我们也可以重新指向新的函数。
现在假定我们想统计一下代码一共调用了多少次parseInt(),可以把所有的调用都找出来,然后手动加上count += 1,不过这样做太傻了。最佳方案是用我们自己的函数替换掉默认的parseInt():
var count = 0;
var oldParseInt = parseInt; // 保存原函数
window.parseInt = function () {
count += 1;
return oldParseInt.apply(null, arguments); // 调用原函数
};
// 测试:
parseInt('10');
parseInt('20');
parseInt('30');
count; // 3
4.高阶函数
JavaScript的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数
一个最简单的高阶函数:
function aHigh_Order_Function(a,b,f){
return f(a)+f(b);
}
var aF =function funAdd(x){
return x*x;
}
console.log(aHigh_Order_Function(1,2,aF));//5
[1]map
map()方法定义在JavaScript的Array中,我们调用Array的map()方法,传入我们自己的函数,就得到了一个新的Array作为结果
这是个map函数 不是ES6中的Map数据类型
function pow(x) {
return x * x;
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81]
console.log(arr);
console.log(arr.map(pow));
[2]reduce
Array的reduce()把一个函数作用在这个Array的[x1, x2, x3...]上,这个函数必须接收两个参数,reduce()把结果继续和序列的下一个元素做累积计算,其效果就是
`函数必须接收两个参数`
1.
function pow(x,y) {
return x * y;
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(arr.reduce(pow));//1*2*3*4*5*6*7*8*9
2.
var aValue = arr.reduce(function aFun(a,b){
return a-b;
});
console.log(aValue);//1-2-3-4-5-6-7-8-9
[3]filter
filter也是一个常用的操作,它用于把Array的某些元素过滤掉,然后返回剩下的元素。和map()类似,Array的filter()也接收一个函数。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。
//保留偶数组成的数组
var arr = [1, 2, 4, 5, 6, 9, 10, 15];
var r = arr.filter(function (x) {
return x % 2 == 0;
});
console.log(r);
[4]sort
Array的sort()方法默认把所有元素先转换为String再根据ASCII码进行排序
sort()方法会直接对Array进行修改
sort()方法也是一个高阶函数,它还可以接收一个比较函数来实现自定义的排序。
var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
if (x < y) {
return -1;
}
if (x > y) {
return 1;
}
return 0;
}); // [1, 2, 10, 20]
5.闭包
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回
[1].函数作为返回值
函数作为结果值返回
function lazy_sum(arr) {
var sum = function () {
return arr.reduce(function (x, y) {
return x + y;
});
}
return sum;
}
//当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数
var afunction = lazy_sum([1,2,3]);//只是一个函数
console.log(afunction());//这个才是结果6
当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数
var f1 = lazy_sum([1, 2, 3, 4, 5]);
var f2 = lazy_sum([1, 2, 3, 4, 5]);
f1 === f2; // false
[1].函数作为返回值
注意1.到返回的函数在其定义内部引用了局部变量arr,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用
2.返回的函数并没有立刻执行,而是直到调用了f()才执行
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push(function () {
return i * i;
});
}
return arr;
}
var results = count();
var f1 = results[0];//16
var f2 = results[1];//16
var f3 = results[2];//16
全部都是16!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了4,因此最终结果为16。
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
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); // 9
理论上讲,创建一个匿名函数并立刻执行可以这么写:
function (x) { return x * x } (3);
但是由于JavaScript语法解析的问题,会报SyntaxError错误,因此需要用括号把整个函数定义括起来:
(function (x) { return x * x }) (3);
通常,一个立即执行的匿名函数可以把函数体拆开,一般这么写:
(function (x) {
return x * x;
})(3);
闭包使用实例1.
在没有class机制,只有函数的语言里,借助闭包,同样可以封装一个私有变量
function create_add(initial) {
var x = initial || 0;
return {
inc: function () {
x += 1;
return x;
}
}
}
var c1 = create_add();
console.log(c1.inc());//1
console.log(c1.inc());//2
console.log(c1.inc());//3
var c2 = create_add(10);
console.log(c2.inc());//11
console.log(c2.inc());//12
console.log(c2.inc());//13
在返回的对象中,实现了一个闭包,该闭包携带了局部变量x,并且,从外部代码根本无法访问到变量x。换句话说,闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来。
闭包还可以把多参数的函数变成单参数的函数
闭包使用实例2.
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
6.箭头函数
1.ES6标准新增了一种新的函数:Arrow Function(箭头函数)。
var fn = x => x*x;
相当于
var fn1 = function(x){
return x * x;
}
2.箭头函数相当于匿名函数,并且简化了函数定义。箭头函数有两种格式,一种像上面的,只包含一个表达式,连{ ... }和return都省略掉了。还有一种可以包含多条语句,这时候就不能省略{ ... }和return:
x => {
if (x > 0) {
return x * x;
}
else {
return - x * x;
}
}
如果参数不是一个,就需要用括号()括起来:
//两个参数
var aFun1 = (a,b)=>a*a+b*b;
console.log(aFun1(1,2));
//没有参数
var aFun2 = ()=>3.14;
console.log(aFun2());
//多个参数
var aFun3 = (a,b,c,...rest){
var i, sum = a + b;
for (i=0; i<rest.length; i++) {
sum += rest[i];
}
return sum;
}
3.如果要返回一个对象,就要注意,如果是单表达式,这么写的话会报错:
// SyntaxError:
x => { foo: x }
因为和函数体的{ ... }有语法冲突,所以要改为:
// ok:
x => ({ foo: x })
4.箭头函数看上去是匿名函数的一种简写,但实际上,箭头函数和匿名函数有个明显的区别:箭头函数内部的this是词法作用域,由上下文确定。
由于JavaScript函数对this绑定的错误处理,下面的例子无法得到预期结果:
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = function () {
return new Date().getFullYear() - this.birth; // this指向window或undefined
};
return fn();
}
};
现在,箭头函数完全修复了this的指向,this总是指向词法作用域,也就是外层调用者ob
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
return fn();
}
};
obj.getAge(); // 25
7.generator
除了return语句,还可以用yield返回多次。
afunc(3)仅仅是创建了一个generator对象,还没有去执行它
`1.不断地调用generator对象的next()方法:`
function* afunc(x){
yield x;
yield x*x;
return x*x*x;
}
var a = afunc(3);
console.log(a.next());//{ value: 3, done: false }
console.log(a.next());//{ value: 9, done: false }
console.log(a.next());//{ value: 27, done: true }
console.log(a.next());//{ value: undefined, done: true }
`第二个方法是直接用for ... of循环迭代generator对象`
for (var x of afunc(2)) {
console.log(x);//2,4 不会打印8 需要把return x*x*x;改成yield x*x*x;
}
function* next_id(x){
for(;;){
yield x++;
}
}
var a = next_id(5);
console.log(a.next());
console.log(a.next());
console.log(a.next());
console.log(a.next());
console.log(a.next());