JavaScript之函数篇

JavaScript中的函数可以像变量一样使用,具很强的抽象能力。

此文章内容是学习廖雪峰的JavaScript教程的知识点总结。


一、函数的定义和调用

1. 函数定义

第一种定义方法:

function name(x1,x2,...x){
//函数体
return}

JavaScript定义的函数实际是一个函数对象,函数名可视为指向函数的变量。

第二种定义方法(匿名函数)

var name = function(x1, x2, ...,x){
//函数体
return  ;
};

直接将匿名函数赋值给变量,通过变量即可调用函数,需在结尾加上;表示赋值语句结束。

注意:函数体内的语句只要执行到return语句就会结束,并返回结果;若无return语句,执行完语句后会返回结果undefined

2. 调用函数

  1. 按顺序传入参数,传入参数多于定义参数可正常调用,少的话计算结果返回NaN。
  2. 关键字arguments:只在函数内部起作用,指向当前调用函数传入的所有参数,类似Array(拥有length属性,通过arguments[i]获得传入的参数。
  3. 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. 变量声明及其作用域

  1. 变量作用域
    var声明的变量是有作用域的。
  • 在函数内部声明的变量,作用域为整个函数体;
  • 嵌套函数中,内部函数可访问外部函数的变量,反之不行;
  • 内外部函数重名,内部函数屏蔽外部变量。
  1. 变量提升
    JavaScript中的函数会将所有变量的声明提升到函数顶部,但不会提升变量的赋值,所以需遵守“在函数内部首先声明所有变量”的规则。

  2. 全局作用域
    不在函数体内定义的变量均具有全局作用域,JavaScript有一个默认的全局对象window,全局作用域的变量都会被绑定为window的一个属性。
    直接访问namewindow.namefoo()window.foo()是一样的。

  3. 名字空间
    全局变量都会被绑定到window上,不同的JS文件如果使用了相同的全局变量或顶层函数名都会造成命名冲突。
    减少冲突的一个方法:将所有的变量和函数全部绑定到一个全局变量(命名空间)中。

  4. 局部作用域
    由于JavaScript的变量作用域实际上是函数内部,所以在for循环等语句块中引进了let关键字(ES6)来声明块级作用域的变量。

  5. 常量
    用关键字const(ES6)来声明常量,命名为全大写。

  6. 总结

  • 关键字varlet是声明变量的,const是声明常量的。
  • var在函数体内声明的具有变量作用域,在函数体外声明的具有全局作用域;constlet具有块级作用域。

2. 解构赋值(ES6)

  1. 使用解构赋值,可以同时对一组变量进行赋值。
var [x, y, z] = ['A', 'B', 'C'];//直接对多个变量赋值
let [x, [y, z]] = ['A', ['B', 'C']];//嵌套层次和位置需保持一致
let [ , , z] = ['A', 'B', 'C'];//忽略前两个元素,只对z赋值
  1. 对对象使用解构赋值
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});//正确
  1. 解构赋值的使用场景(简化代码)
  • 交换两个变量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就可以获得personbrith属性,要保证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;//在方法内部一开始就捕获this
		function getAge() {
			var y = new Date().getFullYear();
			//return y - this.birth;此时的this指向window或undefined
			return 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():接收一个函数作为参数,依次作用于原数组中的每个元素,返回truefalse来判断是否保留该元素。

arr.filter(function(element, index, self));

filter()接收的回调函数可接受三个参数:元素(必选)、索引和数组本身(可选)。

3. sort

  • 排序的核心是比较两个数的大小,Array中的sort()方法规定:对于两个元素xy,若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():判断数组的所有元素是否满足测试条件,返回值:truefalse
(2)find():查找符合条件的第一个元素,找到返回查找的元素,否则返回undefined
(3)findIndex():查找符合条件的第一个元素,找到返回元素对应的索引,否则返回-1
(4)forEach():和map()类似,将每个元素依次作用于传入的函数,但不会返回新的数组(传入函数不需要返回值),常用于遍历数组。


五、闭包

什么是闭包?
可以访问另一个函数作用域变量的函数,一般是定义在外层函数中的内层函数。

为什么要使用闭包?

  • 局部变量无法共享和长久保存,全局变量又可能造成变量污染,所以当变量想反复使用又想避免全局污染时可使用闭包。
  • 使用闭包可以间接访问一个局部变量(达到隐藏变量的目的)。

如何使用闭包?

  1. 定义外层函数,封装一个局部变量;
  2. 定义内层函数,执行对外部函数变量的操作;
  3. 外层函数返回内层函数的对象;
  4. 用一个全局变量调用外层函数,内层函数就被保留在该变量中,在需要的时候用变量调用即可获得内层函数返回的结果。

闭包的特性

  • 函数嵌套函数(造出一个局部变量);
  • 函数内部可以引用函数外部的参数和变量;
  • 参数和变量永远不会被垃圾回收机制回收。

六、箭头函数(ES6)

  1. 箭头函数相当于匿名函数,并简化了函数的定义。
x => x * x;//只包含一个表达式
(x, y) => {//包含多个语句
...
return ;
}
x => ({foo: x});//返回一个对象且是单表达式
  1. 箭头函数与匿名函数的明显区别:箭头函数的this是指向词法作用域的,也就是外层调用者。
var obj = {
	birth: 2000,
	getAge: function () {
		var b = this.birth;//2000
		var fn = () => new Date().getFullYear() - this.birth;//this指向obj对象
		return fn();		
	}
};
obj.getAge();//15
  1. 在箭头函数中,this按照词法作用域绑定,用call()或者apply()调用箭头函数时,无法对 this绑定(传入的第一个参数被忽略)。
var obj = {
	birth: 2000;
	getAge: function(year){
		var b =this.birth; //2000
		var fn = (y) => y-this.birth;//birth为2000
		return 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对象的方法:
  1. 不断调用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的返回值,donetrue时表明generator对象已经执行完毕。

  1. 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,...
}
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页