[JavaScript语法学习]全面介绍函数
函数
ES6语法支持rest参数
rest参数只能定义在参数最后面,用...标识。如果传入的参数连正常定义的参数都没有填满,则rest参数会接收一个空数组。
变量作用域
用var声明的变量实际上都是有作用域的,内部函数可以访问外部函数定义的变量,查找变量时都是从自身函数定义开始,从内向外查找。如果内部函数定义了与外部函数重名的变量,则内部函数的变量将屏蔽外部函数的变量。变量作用域和函数作用域都是在定义时就确定好了的,而不是由运行时决定。但是this变量却是运行时决定的。
变量提升
JS函数会先扫描整个函数体的语句把所有声明的变量提升到函数顶部。因此建议在函数内部定义变量时请严格遵守“函数内部首先声明所有变量”规则。
全局作用域
不在任何函数内定义的变量都具有全局作用域,默认全局对象window
namespace命名空间
将所有的变量和函数全部绑定在一个全局变量中
局部作用域
ES5没有块级作用域,因此在ES6中,引入了let关键字。用let代替var可以声明一个块级作用域变量
常量
在ES6中引入了const关键字来定义常量,const和let都具有块级作用域
this
在一个独立的函数作用域中,如果this没有指定某个对象。那么在严格模式下this指向undefined,在非严格模式下this指向window
如果更改this的指向呢?
1. apply方法可以指定函数的this指向,接收两个参数,第一个是this需要绑定的变量,第二个表示函数本身的参数数组
2. call方法会把参数按照顺序打包成Array进行传入
普通函数的调用通常是把this绑定为null
var count = 0; var oldParseInt = parseInt; window.parseInt = function(){ count+=1; return oldParseInt.apply(null, arguments); };
闭包
在函数内部定义的函数能够访问外部函数的局部变量的特性被称为闭包
经典大坑:返回函数不要引用任何循环变量或者后续会发生变化的变量
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]; var f2= results[1]; var f3= results[2]; f1(); f2(); f3(); // 这三个函数的返回值都是16
ES6对函数的扩展
1. 函数参数的默认值
ES6可以给函数的参数指定默认值,可以避免在ES5中使用 || 的语法实现默认值
但是这样有一个缺陷,或语句逻辑表达式的第一个参数的布尔值如果为false那么也会使用默认值。 比如传入一个空字符串,转换为布尔值就是false
所以一般都要先判断该变量是否有值
// ES5 语法 function log(x, y){ if(typeof y == 'undefined'){ y = 'world'; } } function log(x, y){ if(arguments.length == 1){ y = 'world'; } }
在ES6中,直接在参数定义后面使用等号进行默认值赋值
function log(x, y = "world"){ console.log(x, y); } log("hello"); // hello world log("hello", "es6"); // hello es6 log("hello", ""); // hello
参数变量是默认声明的,所以不能在函数体内再次使用 let 或者 const 声明。
小坑:
如果函数参数是一个对象,但是并没有给整个对象赋予默认值时。那么调用函数时如果不传入参数,则会报错。
最后时刻警惕下列情况
function m1({x = 0 , y = 0} = {}){ return [x, y]; } function m2({x, y} = {x: 0, y: 0}){ return [x, y]; }
参数默认值的位置必须是最后一个函数参数,如果不是最后一个参数,则有默认值的参数需要显示传入undefined才能跳过该参数,而null是无法跳过的。这一点可以参考对象的解构赋值
2. 函数的length属性
函数的length属性 = 函数的参数个数 - 指定了默认值的参数个数
rest参数也不会计入length属性
3. 作用域
如果参数默认值是一个变量,则该变量所处的作用域与其他变量的作用域规则是一样的。先是当前函数作用域,再是全局作用域。
如果函数调用时默认值变量尚未在函数内部生成时,那么该变量会指向全局作用域。
如果函数调用时默认值是一个函数,那么函数作用域是声明时所在的作用域,也就是全局作用域。
4. rest参数
ES6引入了Rest参数,Rest参数必须是最后一个参数,也就是得放在普通参数和默认值参数之后。
Rest参数可以代替arguments变量
const sortNumbers = () => [].prototype.slice.call(arguments).sort();
const sortNumbers = (...numbers) => numbers.sort();
Rest参数变量代表一个数组,所以数组方法都可以用在这个变量上。
function push(array, ...items){ items.forEach( (item) => { array.push(item); }); } var arr = []; push(arr, 1, 2, 3, 4);
5. spread扩展运算符
可以将一个数组转换为用逗号分隔的参数序列,用途:
1. 替代数组的apply方法
2. 合并数组
3. 与解析赋值结合
4. 函数返回值解析
5. 字符串解析
6. 类数组对象的解析
6. 函数的name属性
ES6才将name属性写进标准中,但是很多浏览器都已经实现了这个功能
另外ES6规定了匿名函数也是有name属性的,也会返回实际的函数名。而在大部分浏览器中则依然返回空字符串
Function构造函数返回的函数实例,name属性 = 'anonymous'
函数通过bind()方法进行this绑定后,返回的函数的name属性会在原先的name属性值上添加"bound "前缀
7. 箭头函数 (重点中的重点)
1. 如果函数不需要参数或者需要多个参数,需要使用()将参数部分包围。 仅仅对于只有一个参数的函数可以省略掉()
2. 如果函数体多于一条语句,需要使用{}将函数体包围。并使用return 语句返回。 对于一条语句可以省略{}
3. 如果函数返回的是对象,那么就需要在{}外面套上()
4. 函数体内的this对象已经改变了,表示的是函数定义时所在的对象。而不是原来的使用时所在的对象。
5. 箭头函数不能作为构造函数进行使用
6. 箭头函数中不能使用arguments对象
7. 箭头函数中不能使用yield语句
8. 箭头函数实质上是没有this的,同时arguments, super, new.target也都不存在