函数篇"/>
JavaScript之函数篇
JavaScript之函数篇
- 一、函数的定义和调用
- 1. 函数定义
- 2. 调用函数
- 二、变量作用域与解构赋值
- 1. 变量声明及其作用域
- 2. 解构赋值(ES6)
- 三、方法
- 1.关键字`this`
- 2.`apply`
- 四、高阶函数
- 1. map/reduce
- 2. filter
- 3. sort
- 4. Array
- 五、闭包
- 六、箭头函数(ES6)
- 七、generator
JavaScript中的函数可以像变量一样使用,具很强的抽象能力。
此文章内容是学习廖雪峰的JavaScript教程的知识点总结。
一、函数的定义和调用
1. 函数定义
第一种定义方法:
function name(x1,x2,...x){
//函数体
return ;
}
JavaScript定义的函数实际是一个函数对象,函数名可视为指向函数的变量。
第二种定义方法(匿名函数)
var name = function(x1, x2, ...,x){
//函数体
return ;
};
直接将匿名函数赋值给变量,通过变量即可调用函数,需在结尾加上;
表示赋值语句结束。
注意:函数体内的语句只要执行到return
语句就会结束,并返回结果;若无return
语句,执行完语句后会返回结果undefined
。
2. 调用函数
- 按顺序传入参数,传入参数多于定义参数可正常调用,少的话计算结果返回NaN。
- 关键字
arguments
:只在函数内部起作用,指向当前调用函数传入的所有参数,类似Array(拥有length属性,通过arguments[i]
获得传入的参数。 rest
参数(ES6):以数组的形式储存传入的多余参数,若传入参数不够,rest参数会接受一个空数组。
function foo(x1, x2, ...rest){console.log('a=' + a);console.log('b=' + b);console.log(rest);
}
foo(1,2,3,4,5);
/* 结果:
a = 1
b = 2
Array[3,4,5]
*/foo(1);
/* 结果:
a = 1
b = undefined
Array[]
*/
二、变量作用域与解构赋值
1. 变量声明及其作用域
- 变量作用域
var
声明的变量是有作用域的。
- 在函数内部声明的变量,作用域为整个函数体;
- 嵌套函数中,内部函数可访问外部函数的变量,反之不行;
- 内外部函数重名,内部函数屏蔽外部变量。
-
变量提升
JavaScript中的函数会将所有变量的声明提升到函数顶部,但不会提升变量的赋值,所以需遵守“在函数内部首先声明所有变量”的规则。 -
全局作用域
不在函数体内定义的变量均具有全局作用域,JavaScript有一个默认的全局对象window
,全局作用域的变量都会被绑定为window
的一个属性。
直接访问name
和window.name
、foo()
和window.foo()
是一样的。 -
名字空间
全局变量都会被绑定到window
上,不同的JS文件如果使用了相同的全局变量或顶层函数名都会造成命名冲突。
减少冲突的一个方法:将所有的变量和函数全部绑定到一个全局变量(命名空间
)中。 -
局部作用域
由于JavaScript的变量作用域实际上是函数内部,所以在for
循环等语句块中引进了let
关键字(ES6)来声明块级作用域的变量。 -
常量
用关键字const
(ES6)来声明常量,命名为全大写。 -
总结
- 关键字
var
和let
是声明变量的,const
是声明常量的。 var
在函数体内声明的具有变量作用域,在函数体外声明的具有全局作用域;const
和let
具有块级作用域。
2. 解构赋值(ES6)
- 使用解构赋值,可以同时对一组变量进行赋值。
var [x, y, z] = ['A', 'B', 'C'];//直接对多个变量赋值
let [x, [y, z]] = ['A', ['B', 'C']];//嵌套层次和位置需保持一致
let [ , , z] = ['A', 'B', 'C'];//忽略前两个元素,只对z赋值
- 对对象使用解构赋值
var person = {name: 'Lucy',age: 20,gender: 'demale',passport: 'G-1234567',address:{city: 'WuHan',street: 'LuMo Road',zipcode:'100001'}
};
//快速获取对象中的指定属性进行赋值
var {name, age, passport} = person; //对嵌套对象属性赋值需保持对应层次一致
var {name, address: {city, zip}} = person;//zip为undefined,无对应属性//使用的变量名和属性名不一致
let {name, passport:id} = person;//将passport的属性赋值给id//解构赋值可使用默认值
var {name, single = true} = person;//person中无single属性,使用默认值true/*若变量已经被声明,对其解构赋值时需要用“()”括起来,
不然会将{开头的语句当作块处理,“=”就会不合法*/
{x, y} = {name: 'Lucy', x:100, y:200};//报语法错误
({x, y} = {name: 'Lucy', x:100, y:200});//正确
- 解构赋值的使用场景(简化代码)
- 交换两个变量x和y值时
var x = 1, y = 2;
[x, y] = [y, x];//不需要临时变量
- 快速获取当前页面的域名和路径
var {hostname: domain, pathname: path} = location;
- 一个函数接受一个对象作为参数时,可以使用解构直接把对象的属性绑定到变量中。
//快速创建一个date对象
function buildDate({year, month, day, hour = 0, minute = 0, second = 0}) {return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' +second);
}
//只需要只传入年月日三个属性,也可传入其他属性
buildDate({year: 2020, month: 9, day: 14});
三、方法
在一个对象中绑定函数,称为这个对象的方法。
var person = {name: 'Lucy',birth: 2000,age: function () { //age()就是person的一个方法var y = new Date().getFullYear();return y - this.birth;}person.age();//获得Lucy的年龄:20
};
1.关键字this
始终指向当前对象(person),所以用this.brith
就可以获得person
的brith
属性,要保证this
的指向只能object.xxx()
的形式调用。
若拆开写为:
function getAge() { //age()就是person的一个方法var y = new Date().getFullYear();return y - this.birth;}
var person = {name: 'Lucy',birth: 2000,age: getAge
};
person.age();//正常结果:20
getAge();//NaN
注意:在函数内部调用this
,this的指向:
(1)以对象的方法形式调用,person.age(),该函数的this指向被调用的对象person;
(2)单独调用函数,getAae(),该函数的this指向全局对象window;
(3)在strict模式下,直接调用函数getAae(),this指向undefined;
(4)在方法函数内部定义的函数中(例如重构),this指向window(非strict模式)或undefined(strict模式)。解决办法:用一个that
变量先捕获this
。
’use strict‘;var person = {name: 'Lucy',birth: 2000,age: function () {var that = this;//在方法内部一开始就捕获thisfunction getAge() {var y = new Date().getFullYear();//return y - this.birth;此时的this指向window或undefinedreturn y - that.birth;}return getAge();}person.age();//获得Lucy的年龄:20
};
2.apply
(1)在独立函数中,可以通过函数本身的apply
方法控制this
的指向。apply
接收两个参数,第一个是需要绑定的this变量,第二个是Array(函数本身的参数)。
//用apply修复getAge()的调用
getAge();//NaN
getAge.apply(person, []);//获得Lucy的年龄:20
(2)与apply()类似的方法call()
:前者把参数打包成Array再传入,后者将参数按顺序传入。
Math.max.apply(null, [3, 1, 5]);//5
Math.max.call(null, 3, 1, 5);//5
对于普通函数的调用,通常把this绑定为null
。
(3)利用apply
可以动态改变函数的行为。JavaScript的所有对象都是动态的,即使是内置的函数,也可以重新指向新的函数。
例子:对默认的parseInt()进行修改,统计代码一共用了多少次parseInt():
var count = 0;
var oldParseInt =parseInt;//保留原函数window.parseInt = function(){count += 1;return oldParseInt.apply(null, arguments);//调用原函数
};
//测试
parseInt('1');
parseInt('2');
parseInt('3');
console.log('count=' + count);//3
四、高阶函数
高阶函数:接收另一个函数作为参数的函数。
高阶函数不仅可以接收函数作为参数,还可以将函数作为返回值。(闭包)
1. map/reduce
map()
是定义在Array
中的一种方法,此方法的参数是一个函数,函数的参数就是按顺序调用的原数组元素,将结果组成一个新的数组返回,常用于遍历数据。
array.map(function(currentValue, index, arr))
//currentValue(必选):当前元素的值
//index(可选):当前元素的索引
//arr(可选):当前元素所属的数组对象
reduce()
也是Array
中定义的一种方法,此方法作为参数的函数必须接收两个参数(按顺序调用原数组中的元素),并把每一次返回的结果与原数组的下一个元素作为参数再次调用函数(累计计算),效果为:[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)
。
//对一个Array求和
var arr = [1, 2, 3, 4];
arr.reduce(function(x, y){return x + y;
})//10
2. filter
用于过滤原数组的某些元素,返回剩下元素。
Array
中的filter()
:接收一个函数作为参数,依次作用于原数组中的每个元素,返回true
或false
来判断是否保留该元素。
arr.filter(function(element, index, self));
filter()
接收的回调函数可接受三个参数:元素(必选)、索引和数组本身(可选)。
3. sort
-
排序的核心是比较两个数的大小,Array中的
sort()
方法规定:对于两个元素x
和y
,若x<y
,则返回-1
;若x==y
,则返回0
;若x>y
,则返回1
,不用关心具体的比较过程,直接根据比较结果排序。 -
Array的
sort()
方法默认将所有元素转换为String
,然后根据ASCII码中的大小排序。 -
sort()
是一个高阶函数,可接受一个比较函数来实现自定义排序。 -
sort()
会直接修改Array,返回的结果就是修改后的Array。
var arr = [10, 20, 1, 2];
arr.sort();//[1, 10, 2, 20]按ACSCII码的大小排序//自定义按数字大小排序
arr.sort(function(x, y){if(x < y){return -1;}if(x >y){return 1;}return 0;
});
console.log(arr);//[1, 2, 10, 20]
4. Array
Array中的其他高阶函数:
(1)every()
:判断数组的所有元素是否满足测试条件,返回值:true
或false
。
(2)find()
:查找符合条件的第一个元素,找到返回查找的元素,否则返回undefined
。
(3)findIndex()
:查找符合条件的第一个元素,找到返回元素对应的索引,否则返回-1
。
(4)forEach()
:和map()
类似,将每个元素依次作用于传入的函数,但不会返回新的数组(传入函数不需要返回值),常用于遍历数组。
五、闭包
什么是闭包?
可以访问另一个函数作用域变量的函数,一般是定义在外层函数中的内层函数。
为什么要使用闭包?
- 局部变量无法共享和长久保存,全局变量又可能造成变量污染,所以当变量想反复使用又想避免全局污染时可使用闭包。
- 使用闭包可以间接访问一个局部变量(达到隐藏变量的目的)。
如何使用闭包?
- 定义外层函数,封装一个局部变量;
- 定义内层函数,执行对外部函数变量的操作;
- 外层函数返回内层函数的对象;
- 用一个全局变量调用外层函数,内层函数就被保留在该变量中,在需要的时候用变量调用即可获得内层函数返回的结果。
闭包的特性
- 函数嵌套函数(造出一个局部变量);
- 函数内部可以引用函数外部的参数和变量;
- 参数和变量永远不会被垃圾回收机制回收。
六、箭头函数(ES6)
- 箭头函数相当于匿名函数,并简化了函数的定义。
x => x * x;//只包含一个表达式
(x, y) => {//包含多个语句
...
return ;
}
x => ({foo: x});//返回一个对象且是单表达式
- 箭头函数与匿名函数的明显区别:箭头函数的
this
是指向词法作用域
的,也就是外层调用者。
var obj = {birth: 2000,getAge: function () {var b = this.birth;//2000var fn = () => new Date().getFullYear() - this.birth;//this指向obj对象return fn(); }
};
obj.getAge();//15
- 在箭头函数中,
this
按照词法作用域绑定,用call()
或者apply()
调用箭头函数时,无法对 this绑定(传入的第一个参数被忽略)。
var obj = {birth: 2000;getAge: function(year){var b =this.birth; //2000var fn = (y) => y-this.birth;//birth为2000return fn.call({birth:2010},year);}
};
obj.getAge(2020);//20
七、generator
generator
生成器,ES6引入的新的数据类型,形似函数,但可以返回多次,由function*
定义,除了return
语句,还可用yield
返回多次。
function* foo(x){yield x + 1;yield x + 2;return x + 3;
}
- 调用generator对象的方法:
- 不断调用generator对象的
next()
方法:
var f = fun(n);
f.next();//{value: ; done: false}
f.next();//value: ; done:false}
...
f.next();//{value: undefined, done: true}
next()
方法会执行generator的代码,每遇到yield x;就会返回一个对象,然后暂停,value
就是yield
的返回值,done
为true
时表明generator对象已经执行完毕。
- 用
for...of
循环迭代generator对象
function* fib(max){//0 1 1 2 3 5 8 13 21 34...var t, a = 0, b = 1, n = 0;while (n < max){yield a;[a, b] = [b, a + b];n ++;}return;
}
for(var x of fib(10)){console.log(x);//依次输出:0,1,1,2,3,5,...
}
更多推荐
JavaScript之函数篇
发布评论