函数
函数定义构成
1.函数名称标识符。
函数名称是函数声明语句的必须部分。
2.一对圆括号。
包含0个或多个由逗号隔开的标识符,这些为函数的形参。
3.一对花括号。
其中包含0条或多条JavaScript语句。这些语句构成了函数体:一旦调用函数,就会执行这些语句。
函数只会执行到函数体中的return语句处,其后面的语句不再执行。
函数定义方式
1.函数声明语法
function add(num1, num2) {
return num1 + num2;
};
2.函数表达式
let sub = function (num1, num2) {
return num1 - num2;
};
若使用函数表达式创建的函数,函数标识符只能在该函数的作用域内使用,因此若使用函数标识符调用函数,则会报错:该函数标识符未被定义。若在函数内部输出该函数标识符,则会返回该函数代码。
※ 函数声明与函数表达式的区别
函数声明会将函数提升,函数表达式不能将函数提升,提升的是变量,因此,若用函数表达式定义函数时,要先定义才能调用。
3.箭头函数
let mul = (mun1, num2) => num1 * num2;
let mul = (mun1, num2) => {
return num1 * num2;
};
// 当函数体只有一句return语句时,可以省略花括号。
let f1 = () => {
console.log("arrow function");
};
let f2 = x => x ** 2;
// 当函数有多个形参时,要用圆括号将其括起来。
let xx = ((x) => x ** x)(4);
// 箭头函数也可以立即调用,先将圆括号将函数括起来,后面在跟一个圆括号将实参进行传递。
4.Function构造函数
这个构造函数接收任意多个字符串参数,最后一个参数始终会被当成函数体,而之前的参数都是新函数的参数。
let sum = new Function(
"num1",
"num2",
"let result=num1+num2; return result;"
);
※ 不推荐使用这种语法来定义函数,因为这段代码会被解释两次:第一次是将它当作常规ECMAScript 代码,第二次是解释传给构造函数的字符串。这显然会影响性能。
Arguments对象
一个类数组对象(但不是 Array 的实例),因此可以使用中括号语法访问其中的元素。
要确定接收到的参数个数,可以访问 arguments.length 属性。
- 函数表达式内部支持arguments 对象,而箭头函数内部不支持arguments对象。
- Arguments包含剩余参数,若剩余参数没有接受,则是一个空数组。
参数默认值
Es6中增加了参数默认值,可以在定义函数的时候对形参进行默认值的赋值,当没有实参进行传递或者传递的实参类型为undefined或实参是undefined时,该参数则将为默认值。
function f2(name = "User", age = 0) {
console.log(name, age);
};
扩展参数
function sum() {
let r = 0;
for (let i = 0; i < arguments.length; i++) {
r += arguments[i];}
return r;
}
let nums = [1, 2, 3, 4, 5];
// 如果要把nums当做实参传到sum()时,则要用到扩展运算符
console.log(sum(...nums));
// 此时就能把nums中所有的元素传入到函数中。
剩余参数
function sum2(name, ...scores) {
let r = scores.reduce((x, y) => x + y, 0);
console.log(`${name}总分为:${r}。`);
};
sum2("Tom", 80, 90, 100); // Tom总分为:270。
剩余参数会将实参中剩余的参数全部传入到函数中,且剩余参数只能位于参数列表的末尾。
※ 扩展参数位于函数的实参位置,而剩余参数则位于函数的形参位置。
函数调用
构成函数主体的JavaScript代码在定义之时并不会执行,只有调用该函数时,它们才会执行。
有4种方式来调用JavaScript函数:
- 作为函数
- 作为方法
- 作为构造函数
- 通过它们的call()和apply()方法间接调用
方法调用
方法是属于某个特定对象才能调用的函数。
调用上下文
1.关键字this没有作用域的限制,嵌套的函数不会从调用它的函数中继承this。
2.This是通过计算得出来的,每个作用域有不同的this
间接调用
使用函数对象的call( )和apply( )方法可以间接调用函数。
第一个参数指定调用上下文(函数内部的this),第二个参数给函数传递参数。
回调函数
被作为实参传入另一函数,并在该外部函数内被调用,用以来完成某些任务的函数,称为回调函数。
函数属性
length属性
1.在函数体里,arguments.length表示传入函数的实参的个数。
2.而函数本身的length属性是只读的,它代表函数声明的实际参数的数量。
prototype属性
每一个函数都包含一个prototype属性,这个属性是指向一个对象的引用,这个对象称做“原型对象”(prototype object)。
每一个函数都包含不同的原型对象。当将函数用做构造函数的时候,新创建的对象会从原型对象上继承属性。
函数方法
call( )和apply( )方法
通过调用方法的形式来间接调用函数。
call()和apply()的第一个实参是要调用函数的主体对象,它是调用上下文,在函数体内通过this来获得对它的引用。
bind( )方法
- 将函数绑定至某个对象,且可以绑定参数值。
- 当在函数f()上调用bind()方法并传入一个对象o作为参数,这个方法将返回一个新的函数。
- (以函数调用的方式)调用新的函数将会把原始的函数f()当做o的方法来调用。 传入新函数的任何实参都将传入原始函数。
- 通过bind()为函数绑定调用上下文后,返回的函数不能通过call和apply修改调用上下文对象。
示例1:
let obj = {
x: 10,
show: function (y) {
let r = "";
for (let i = 0; i < y; i++) {
r += this.x + " ";
}
console.log(r);
},
};
obj.show(4); // 10 10 10 10
let ss = obj.show.bind({ x: 1000 });
ss(3); // 1000 1000 1000
let sum = function (x, y) {
return x + y;
};
let succ = sum.bind(null, 1);
console.log(succ(2)); // 3
let obj = {};
obj.supAdd = sum.bind(obj, 10);
/* ƒ (x, y) {
* return x + y;}
*/
//偏函数,固定一个函数的一个或者多个参数,返回一个新的函数,这个函数用于接受剩余的参数。
console.log(obj.supAdd(20)); // 30
偏函数的好处:
- 通过创建一个名称易懂的独立函数,调用是无需每次传入第一个参数,因为第一个参数通过bind提供了固定值。
- 当我们有一个很通用的函数,为了方便提供一个较常用的变体。
示例2:
let obj = { z: 1000 };
function sum(x, y) {
let r = x + y;
if (Object.getPrototypeOf(this) != global /*window*/ ) {
console.log(this);
r += this.z;
};
return r;
};
obj.sum = sum;
console.log(obj.sum(20, 30)); // 1050
console.log(obj.sum.call({ z: 1 }, 2, 3)); // 6
// 当函数没有用bind方法进行绑定时,可以通过call方法对调用上下文对象进行修改
obj.sum = sum.bind(obj);
console.log(obj.sum(100, 200)); // 1300
console.log(obj.sum.apply({ z: 1 }, [50, 60])); // 1110
// 当函数用bind方法绑定后,apply方法将不能修改调用上下文的属性值
函数闭包
JavaScript采用词法作用域(lexical scoping)。
- 函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的。
- 为了实现这种词法作用域,JavaScript函数对象的内部状态不仅包含函数的代码逻辑,还必须引用当前的作用域链
。
函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为“闭包(closure)”。
- 从技术的角度讲,所有的JavaScript函数都是闭包:它们都是对象,它们都关联到作用域链。
var scope = "globle scope";
function checkscope() {
var scope = "local scope";
function f() { return scope; }
return f();
}
console.log(checkscope()); // local scope
var scope = "globle scope";
function checkscope() {
var scope = "local scope";
function f() { return scope; }
return f;
}
var ff = checkscope();
console.log(ff()); // local scope
它们可以捕捉到局部变量(和参数),并一直保存下来,看起来像这些变量绑定到了在其中定义它们的外部函数。
闭包原理
- 在JavaScript中,如果一个对象不再被引用,那么这个对象就会被垃圾回收机制回收。
- 每次调用JavaScript函数的时候,都会为之创建一个新的对象(活动对象Activation
Object)用来保存局部变量,把这个对象添加至作用域链中。 - 当函数返回的时候,就从作用域链中将这个绑定变量的对象删除。
1. 如果不存在嵌套的函数,也没有其他引用指向这个绑定对象,它就会被当做垃圾回收掉。
2. 如果定义了嵌套的函数,每个嵌套的函数都各自对应一个作用域链,并且这个作用域链指向一个变量绑定对象。 - 如果这个函数定义了嵌套的函数,并将它作为返回值返回或者存储在某处的属性里,这时就会有一个外部引用指向这个嵌套的函数。
1. 它就不会被当做垃圾回收,并且它所指向的变量绑定对象也不会被当做垃圾回收。