Ch10 函数
声明函数的三种方法:
function sum(num1, num2) {
return num1 + num2;
}
let sum = function (num1, num2) {
return num1 + num2;
}
let sum = new Function("num1", "num2", "return num1 + num2"); //不推荐
10.1 箭头函数
箭头函数(=>)是ES6新特性。比较适合嵌入函数或者回调函数的书写。
使用:
let ints = [1,2,3]
ints.map(function(i) { return i + 1; })
// 等价于
ints.map((i) => i + 1)
当函数体内只有一行代码,比如赋值操作或者一个表达式,就可以不用大括号,并且省略大括号的写法会返回这行代码的执行结果。
10.2 函数名
ES6的所有函数对象都会暴露一个只读属性name,其中包括了关于函数的信息。
function foo(){}
let bar = function(){};
let baz = ()=>{};
foo.name; // foo
bar.name; // bar
baz.name; // baz
(()=>{}).name // (空字符串)
(new Function()).name // anonymous
10.3 函数参数
在js中,函数既不关心传入的参数个数,也不 关心这些参数的数据类型。定义函数时要接收两个参数,并不意味着调用时就传两个参数。你可以传一 个、三个,甚至一个也不传,解释器都不会报错。
在使用function关键字(非箭头)定义函数时,可以在函数体内通过arguments对象获得传进来的参数。arguments是一个类数组对象,并不是数组的实例,因此在对arguments对象使用数组方法时,需要通过call或者apply来实现。如:Array.prototype.slice.call(arguments, 0)
修改arguments中的值会同时影响到函数传进来的参数。但是在严格模式下,即使修改arguments的值,函数参数的值仍然保持不变,而且在函数中尝试重写arguments会报错。
function doAdd(num1, num2) {
arguments[1] = 100;
return num1 + num2;
}
doAdd(1,20); // 101
10.4 没有重载
JavaScript不像其他语言,一个函数可以有两个定义。在Js中,函数没有签名(接收参数的类型和数量),因此没有重载。如果在JS中定义了两个名字相同的函数,那么后定义的会覆盖先定义的。
10.5 默认参数
ECMAScript 6 支持显式定义默认参数了。
function makeKing(name = 'Henry') {
return `King ${name} VIII`;
}
console.log(makeKing('Louis')); // 'King Louis VIII'
console.log(makeKing()); // 'King Henry VIII'
10.6 参数扩展与收集
扩展参数
function getSum() {
let sum = 0;
for (let i = 0; i < arguments.length; ++i) {
sum += arguments[i];
}
return sum;
}
let values = [1, 2, 3, 4];
console.log(getSum(...values)); // 10
收集参数
在构思函数定义时,可以使用扩展操作符把不同长度的独立参数组合为一个数组。
function getSum(...values) {
// 顺序累加 values 中的所有值
// 初始值的总和为 0
return values.reduce((x, y) => x + y, 0);
}
console.log(getSum(1,2,3)); // 6
10.7 函数声明与函数表达式
由于函数提升的存在,JavaScript引擎会在执行代码前读取所有函数声明,并生成函数定义。而函数表达式必须等到代码执行到它那一行,才会在上下文中声明函数定义。
// 函数声明提升
console.log(sum(10, 10));
function sum(num1, num2) {
return num1 + num2;
}
// 会出错
console.log(sum(10, 10));
let sum = function(num1, num2) {
return num1 + num2;
};
10.9 函数内部
在函数内部存在三个特殊对象:arguments、this以及 new.target 。
10.9.1 arguments
arguments 对象是一个类数组对象,包含调用函数时传入的所有参数。这个对象只有以 function 关键字定义函数时才会有。arguments 对象还有一个 callee 属性,是一个指向 arguments 对象所在函数的指针。callee存在的意义是在递归的时候,让函数逻辑与函数名解耦。
// 求阶乘函数
function factorial(num) {
if (num <= 1) {
return 1;
} else {
return num * factorial(num - 1);
// 上面这一行等价于下面一行
return num * arguments.callee(num - 1);
}
}
10.9.2 this
在非箭头函数中,this 引用的是调用该函数的上下文对象(在 网页的全局上下文中调用函数时,this 指向 windows)。
在箭头函数中,this引用的是定义箭头函数的上下文。
这里简单来说归为两点:
- 普通函数的this指向在执行时决定
- 箭头函数的this指向在定义时决定
10.9.3 caller
ES5 给函数对象上添加了一个属性: caller,指向调用当前函数的函数。
function outer() {
inner();
}
function inner() {
console.log(inner.caller);
}
outer(); // f outer(){...}
10.9.4 new.target
ECMAScript 6 新增了检测函数是否使用 new 关键字调用的 new.target 属性。
- 如果函数是正常调用的,则 new.target 的值是 undefined;
- 如果是使用 new 关键字调用的,则 new.target 将引用被调用的构造函数。
10.10 函数属性和方法
JavaScript中的函数是对象,因此会有属性和方法。
每个函数有两个属性:length和prototype。其中length用来表示函数的参数个数; prototype 是保存引用类型所有实例方法的地方。
每个函数还有三个方法:apply,call,bind。这三个方法的作用是以指定的this值来执行函数,简单来说就是改变this指向。
- apply接收两个参数,第一个是this指向,另一个是参数数组(可以是Array实例,也可以是arguments)。
- call作用与apply一样,第一个参数是this指向,后面的参数会逐个传进被调用函数的参数中。
- bind传参与call相同,与call不同的是,它返回另外一个函数实例。
function sum(num1, num2) {
return num1 + num2;
}
function callSum1(num1, num2) {
return sum.apply(this, arguments);
}
function callSum(num1, num2) {
return sum.call(this, num1, num2);
}
function callSum3(num1, num2) {
return sum.bind(this, num1, num2);
}
// 调用callSum3要这么调用
callSum3(1,2)() // 3
10.11 函数表达式
const functionName = function(){}
函数表达式看起来就像一个普通的变量定义和赋值,即创建一个函数再把它赋值给一个变量 functionName。这样创建的函数叫作匿名函数(anonymous funtion),因为 function 关键字后面没有标识符。(匿名函数有也时候也被称为兰姆达函数)。
函数表达式的优点:函数可以根据情况而定,灵活多变。
// 千万别这样做!
if (condition) {
function sayHi() {
console.log('Hi!');
}
} else {
function sayHi() {
console.log('Yo!');
}
}
// 正确做法
let sayHi;
if (condition) {
sayHi = function sayHi() {
console.log('Hi!');
}
} else {
sayHi = function sayHi() {
console.log('Yo!');
}
}
10.14 闭包
闭包指的是引用了另外一个函数作用域中变量的函数,通常实在嵌套函数中实现。
function makeFunc() {
var name = "Mozilla";
return function () {
alert(name); // 闭包
}
}
var myFunc = makeFunc();
myFunc();
10.14.1 this
在闭包中使用 this 会让代码变复杂。如果内部函数没有使用箭头函数定义,则 this 对象会在运行时绑定到执行函数的上下文。匿名函数在这种情况下不会绑定到某个对象,这就意味着 this 会指向 window,
window.identity = 'The Window';
let object = {
identity: 'My Object',
getIdentityFunc() {
return function() {
return this.identity;
};
}
};
console.log(object.getIdentityFunc()()); // 'The Window'
那么我们如何访问到外部函数的this呢?两种方法,一种使用箭头函数,另外一种用变量保存this。
let object = {
identity: 'My Object',
getIdentityFunc() {
return ()=> {
return this.identity;
};
}
};
console.log(object.getIdentityFunc()()); // 'My Object'
let object = {
identity: 'My Object',
getIdentityFunc() {
let that = this;
return ()=> {
return that.identity;
};
}
};
console.log(object.getIdentityFunc()()); // 'My Object'
10.15 立即调用函数表达式IIFE
简单的例子:用IIFE来模拟块级作用域。
// IIFE
(function () {
for (var i = 0; i < count; i++) {
console.log(i);
}
})();
console.log(i); // 抛出错误