函数
函数也是对象的一种,每一个函数都是Function
类型的实例。函数名就是指向函数对象的指针。
声明函数的几种方式
// 声明函数方式
// 函数声名
function sum(a, b) {
return a + b;
}
// 函数表达式
let sum = function (a, b) {
return a + b;
};
// 箭头函数
let sum = (a, b) => {
return a + b;
};
// Function构造函数
let sum = new Function("a", "b", "return a+b");
箭头函数
ES6新增的语法定义函数表达式,使用=>胖箭头。适用于嵌入式函数场景。
// 箭头函数
(参数)=>{表达式};
// 只有一个参数时()可以省略,支持接收多个参数,只有一条执行语句时{}也可以省略。
// 嵌入式场景实例
let ary = [1,2,3];
console.log(ary.map(i=>{return i+1}));//[2,3,4]
箭头函数不能使用arguments参数伪数组、super关键字和new.target,也不能用作构造函数,箭头函数也没有prototype属性。
函数名
函数名是指向函数的指针。因此一个函数可以有多个函数名,他们都指向同一个函数。
当给一个新变量赋于函数时,两个函数名之间就存在了联系,他们都是指向原函数的,当把旧的函数null化后,联系会消失但是新的函数1依旧可以调用,这是因为新函数时直接指向原函数的。
// 函数名
function sum(a, b) {
return a + b;
}
let anSum = sum;
console.log(sum(1, 2));//3
console.log(anSum(2, 3));//5
sum = null;
console.log(sum(1, 2));//sum is not a function
console.log(anSum(2, 3));//5
函数会暴露一个name属性,它包含了函数的信息,通常情况下只有函数的标识符。如下代码,注意打印的式anSum的name但是输出的是sum,这是因为anSum只是一个函数名,他指向的原函数才是真正的函数。
箭头函数name为空白字符串,由Function()直接实例化的函数name为anonymous
console.log(anSum.name);//sum
console.log((()=>{}).name);//一行空白输出
console.log((new Function()).name);//anonymous匿名
参数
JavaScript参数严格意义上并不算参数,只是一个类数组对象。非严格模式下
,arguments对象的值和参数值是同步
的,但是内存地址不同,也就是说可以重写传入的参数,开启严格模式
以后,这两个值就不会相互影响
了,并且不能重写,若重写会导致语法错误。
function test(a, b) {
arguments[0] = 1;//严格模式下不执行
return a + b;
}
console.log(test(3,4));//开启严格输出7,反之输出5
没有重载
JavaScript函数没有签名,因此不能由于函数的参数不同将两个同名不同参函数视为两个函数,只会是新定义覆盖旧定义
。
默认参数值
ES6支持在函数定义是给定默认参数,当调用时没有传入参数就会使用默认参数。默认参数不会被arguments接收,默认参数可以是原始值或者对象也可以是函数的返回值。
默认参数作用域与暂时性死区
函数参数可以是对象,函数,因此会涉及求值,就会涉及作用域,定义默认参数相当于调用了let声名。所以,在没有let声明一个变量之前就调用这个变量会出现暂时性死区错误。
参数扩展与收集
扩展参数...
,对可迭代对象使用,可以将对象作为一个参数传入,将可迭代对象拆分,然后逐个传入函数。
收集参数,在构思函数定义时,用扩展操作符把不同长度的独立参数组合为一个数组,类似于arguments构造。
函数声明与函数表达式
JavaScript对这两者是区别对待的。
函数声明会被预先读取,函数声名提升
,函数表达式必须执行到该表达式才会执行
。
函数作值
函数名是一个变量,因此可以和变量一样用在可以使用变量的地方。
function callSomeFunction(someFunction, someArugment1, someArugment2) {
return someFunction(someArugment1, someArugment2);
}
function sum(a, b) {
return a + b;
}
console.log(callSomeFunction(sum, 1, 2));//3
函数内部
函数内部存在三个特殊对象:arguments
,this
,new.target(ES6新增)
。
arguments
是一个类数组对象,主要用于包含参数。arhuments有一个属性callee,是指向arguments对象坐在函数的指针。
this
在标准函数中,this会把函数当作方法调用的上下文对象,也就是调用普通函数的的对象。
在箭头函数中,this引用的是定义箭头函数的上下文,也就是箭头函数的外层。
caller
caller引用的是调用当前函数的函数,在全局作用域当中则为null。
new.target
这一属性用于区分当前函数是直接调用的还是通过new实例化的,如果只直接调用的值为undefined,如果是new实例化的,那么值就是指向被调用构造函数的指针。
函数属性与方法
函数是对象,有属性和方法。
所有函数都有length和prototype属性。
length
属性包含本函数的参数个数
。
函数从对象上继承的方法都在prototype上。
函数有两个自有方法apply()和call()方法。这两个方法用来改变函数的this对象值
。
apply()方法:
apply(参数1,参数2)接收两个参数
参数1:this值,可以是对象;
参数2:Array实例(可以是arguments(类数组);
call()方法:
call(参数1,参数2,…)接收两个参数
参数1:this值,可以是对象;
参数2:Array实例(可以是arguments(类数组);
…
这个方法与apply的区别在于apply可以将参数以数组或类数组方式传递,而call必须将参数一个一个罗列。
ES5有bind()
方法绑定对象限制作用域。
如何输出函数代码?三种方法
toLocaleString()
、toString()
、valueOf()
前两个方法返回函数代码,最后一个返回函数本身。
函数表达式
创建函数再赋值给变量(functionName),这样的函数叫做匿名函数
。可以利用函数表达式,将函数作为另一个函数的返回值。
递归
递归函数就是一个函数通过名称调用自己。
非严格模式下可以使用arguments.callee
调用递归函数,若给递归函数起别名再调用会报错。arguments.callee
是一个指向正在运行函数的指针。
function fact(num) {
if (num <= 1) {
return 1;
} else {
return num * fact(num - 1);
// return num * arguments.callee(num - 1);
}
}
console.log(fact(4));//24
尾调用以及优化
ES6以前对于如下代码,执行方式为fn1进入栈然后执行,到return fn2,fn2再入栈,执行fn2,fn2结束后将值返回给fn1弹栈,fn1接受值,弹栈,总共有两次入栈弹栈并且栈空间会有两个栈帧。
ES6实现尾调用优化以后,栈空间同一时间只会有一个栈帧。执行fn1入栈,到return fn2,这时将fn1弹栈(因为fn1和fn2的返回结果是相同的),fn2入栈,计算后返回再弹栈。
function fn1() {
return fn2();//尾调用
}
尾调用优化使用的条件:
①代码在严格模式下执行
②外部函数的返回值是对尾调用函数的调用
③尾调用函数返回以后不执行额外的逻辑
④为调用函数不是引用外部用函数作用域中自由变量的闭包
闭包
引用了另一个函数作用域中变量的函数。
函数执行时,每个执行上下文都有一个包含其中变量的对象。全局上下文中称为变量对象,函数局部上下文中称为活动对象,且只在函数执行时棋存在。
一般情况下,函数内部代码在访问变量时,会使用定义的名称从作用域链中查找。函数执行完后,局部活动对象会被销毁。内存里就只剩下全局作用域。但是,闭包并不相同,使用匿名函数,匿名函数的作用域会被初始化为匿名函数的父级函数调用者变量对象+父级函数的变量对象+全局变量对象。
立即调用的函数表达式
立即调用的匿名函数表达式
,类似于函数声明,被包含在括号中,解释为函数表达式,在第一组括号后面的括号会立即调用前面的函数表达式。
(function () {
})();