JavaScript高级程序设计(4)
第10章 函数
### 10.1 箭头函数 288 ```javascript // 如果只有一个参数,那也可以不用括号。以下两种写法都有效 let double = (x) => { return 2 * x; }; let triple = x => { return 3 * x; }; // 没有参数需要括号 let getRandom = () => { return Math.random(); }; // 多个参数需要括号 let sum = (a, b) => { return a + b; }; //箭头函数简洁的语法非常适合嵌入函数的场景: let ints = [1, 2, 3]; console.log(ints.map(function(i) { return i + 1; })); // [2, 3, 4] console.log(ints.map((i) => { return i + 1 })); // [2, 3, 4] //箭头函数也可以不用大括号,那么箭头后面就只能有一行代码,比如一个赋值操作,或者一个表达式。但这样会改变函数的行为。 // 以下两种写法都有效,而且返回相应的值 let double = (x) => { return 2 * x; }; let triple = (x) => 3 * x; // 可以赋值 let value = {}; let setName = (x) => x.name = "Matt"; setName(value); console.log(value.name); // "Matt" // 无效的写法: let multiply = (a, b) => return a * b; ``` ### 10.2 函数名 289 因为函数名就是指向函数的指针,所以它们跟其他包含对象指针的变量具有相同的行为。这意味着一个函数可以有多个名称,如下所示: ```javascript function sum(num1, num2) { return num1 + num2; } console.log(sum(10, 10)); // 20 let anotherSum = sum; console.log(anotherSum(10, 10)); // 20 sum = null; console.log(anotherSum(10, 10)); // 20 ``` ### 10.3 理解参数 290 ECMAScript 函数既不关心传入的参数个数,也不关心这些参数的数据类型。定义函数时要接收两个参数,并不意味着调用时就传两个参数。你可以传一个、三个,甚至一个也不传,解释器都不会报错。之所以会这样,主要是因为 ECMAScript 函数的参数在内部表现为一个数组。事实上,在使用 function 关键字定义(非箭头)函数时,可以在函数内部访问 arguments 对象,从中取得传进来的每个参数值。 ```javascript function sayHi(name, message) { console.log("Hello " + name + ", " + message); } //可以通过 arguments[0]取得相同的参数值。因此,把函数重写成不声明参数也可以: function sayHi() { console.log("Hello " + arguments[0] + ", " + arguments[1]); } ``` ### 10.4 没有重载 292 > ECMAScript 函数不能像传统编程那样重载。如前所述,ECMAScript 函数没有签名,因为参数是由包含零个或多个值的数组表示的。没有函数签名,自然也就没有重载。 ### 10.5 默认参数值 2933 在 ECMAScript5.1 及以前,实现默认参数的一种常用方式就是检测某个参数是否等于 undefined,如果是则意味着没有传这个参数,那就给它赋一个值。ECMAScript 6 之后就不用这么麻烦了,因为它支持显式定义默认参数了。 ```javascript //ECMAScript5.1及以前 function makeKing(name) { name = (typeof name !== 'undefined') ? name : 'Henry'; return `King ${name} VIII`; } //ECMAScript 6 之后 function makeKing(name = 'Henry') { return `King ${name} VIII`; } ``` ### 10.6 参数扩展与收集 295 ##### 10.6.1 扩展参数 ```javascript let values = [1, 2, 3, 4]; function getSum() { let sum = 0; for (let i = 0; i < arguments.length; ++i) { sum += arguments[i]; } return sum; } console.log(getSum.apply(null, values)); // 10 console.log(getSum(...values)); // 10 console.log(getSum(-1, ...values)); // 9 console.log(getSum(...values, 5)); // 15 console.log(getSum(-1, ...values, 5)); // 14 console.log(getSum(...values, ...[5,6,7])); // 28 ``` ##### 10.6.2 收集参数 > 在构思函数定义时,可以使用扩展操作符把不同长度的独立参数组合为一个数组。这有点类似arguments 对象的构造机制,只不过收集参数的结果会得到一个 Array 实例。收集参数的前面如果还有命名参数,则只会收集其余的参数;如果没有则会得到空数组。因为收集参数的结果可变,所以只能把它作为最后一个参数。 ```javascript function getSum(...values) { // 顺序累加 values 中的所有值 // 初始值的总和为 0 return values.reduce((x, y) => x + y, 0); } console.log(getSum(1,2,3)); // 6 ``` ### 10.7 函数声明与函数表达式 297 > JavaScript 引擎在任何代码执行之前,会先读取函数声明,并在执行上下文中生成函数定义。而函数表达式必须等到代码执行到它那一行,才会在执行上下文中生成函数定义。也就是说,函数声明提升会把函数声明提升,但函数表达式不会。 ```javascript // 没问题 console.log(sum(10, 10)); function sum(num1, num2) { return num1 + num2; } ``` ```javascript // 会出错 console.log(sum(10, 10)); let sum = function(num1, num2) { return num1 + num2; }; ``` ### 10.8 函数作为值 297 > 因为函数名在 ECMAScript 中就是变量,所以函数可以用在任何可以使用变量的地方。这意味着不仅可以把函数作为参数传给另一个函数,而且还可以在一个函数中返回另一个函数。 ```javascript function callSomeFunction(someFunction, someArgument) { return someFunction(someArgument); } function add10(num) { return num + 10; } let result1 = callSomeFunction(add10, 10); console.log(result1); // 20 ``` ### 10.9 函数内部 299 ##### 10.9.1 **arguments** > 它是一个类数组对象,包含调用函数时传入的所有参数。这个对象只有以 function 关键字定义函数(相对于使用箭头语法创建函数)时才会有。 ##### 10.9.2 **this** > 在标准函数中,this 引用的是把函数当成方法调用的上下文对象,这时候通常称其为 this 值。在箭头函数中,this引用的是定义箭头函数的上下文。 ##### 10.9.3 **caller** > 这个属性引用的是调用当前函数的函数,或者如果是在全局作用域中调用的则为 null。 ##### 10.9.4 **new.target** > ECMAScript 中的函数始终可以作为构造函数实例化一个新对象,也可以作为普通函数被调用。ECMAScript 6 新增了检测函数是否使用 new 关键字调用的 new.target 属性。如果函数是正常调用的,则 new.target 的值是 undefined;如果是使用 new 关键字调用的,则 new.target 将引用被调用的构造函数。 ### 10.10 函数属性与方法 302 - 属性length: 保存函数定义的命名参数的个数。 - 属性prototype: 保存引用类型所有实例方法的地方,这意味着 toString()、valueOf()等方法实际上都保存在 prototype 上,进而由所有实例共享。这个属性在自定义类型时特别重要。 - 方法apply(this, []): 接收两个参数:函数内 this 的值和一个参数数组,参数数组是Array 的实例或 arguments 对象 - 方法call(this, ): 第一个参数是 this值,而剩下的要传给被调用函数的参数则是逐个传递的。 - 方法bind(): 创建一个新的函数实例,其 this 值会被绑定到传给 bind()的对象。 ```javascript function sum(num1, num2) { return num1 + num2; } function callSum1(num1, num2) { return sum.apply(this, arguments); // 传入 arguments 对象 } function callSum(num1, num2) { return sum.call(this, num1, num2); } window.color = 'red'; var o = { color: 'blue' }; function sayColor() { console.log(this.color); } let objectSayColor = sayColor.bind(o); objectSayColor(); // blue ``` ### 10.11 函数表达式 304 ```javascript //函数声明 function functionName(arg0, arg1, arg2) { // 函数体 } //函数表达式 let functionName = function(arg0, arg1, arg2) { // 函数体 }; ``` ```javascript // 千万别这样做! 多数浏览器会忽略 condition 直接返回第二个声明。这种写法很危险,不要使用。 if (condition) { function sayHi() { console.log('Hi!'); } } else { function sayHi() { console.log('Yo!'); } } // 不过,如果把上面的函数声明换成函数表达式就没问题了 let sayHi; if (condition) { sayHi = function() { console.log("Hi!"); }; } else { sayHi = function() { console.log("Yo!"); }; } ``` ### 10.12 递归 306 ```javascript function factorial(num) { if (num <= 1) { return 1; } else { return num * factorial(num - 1); } } //虽然这样写是可以的,但如果把这个函数赋值给其他变量,就会出问题: let anotherFactorial = factorial; factorial = null; console.log(anotherFactorial(4)); // 报错 //arguments.callee 就是一个指向正在执行的函数的指针,因此可以在函数内部递归调用,无论通过什么变量调用这个函数都不会出问题。因此在编写递归函数时,arguments.callee 是引用当前函数的首选。 function factorial(num) { if (num <= 1) { return 1; } else { return num * arguments.callee(num - 1); } } //不过,在严格模式下运行的代码是不能访问 arguments.callee 的,因为访问会出错。此时,可以使用命名函数表达式(named function expression)达到目的。 const factorial = (function f(num) { if (num <= 1) { return 1; } else { return num * f(num - 1); } }); ``` ### 10.13 尾调用优化 307 > ECMAScript 6 规范新增了一项内存管理优化机制,让 JavaScript 引擎在满足条件时可以重用栈帧。具体来说,这项优化非常适合“尾调用”,即外部函数的返回值是一个内部函数的返回值。 ```javascript function outerFunction() { return innerFunction(); // 尾调用 } ``` ### 10.14 闭包 309 > 匿名函数经常被人误认为是闭包(closure)。闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。 > > 因为闭包会保留它们包含函数的作用域,所以比其他函数更占用内存。过度使用闭包可能导致内存过度占用,因此建议仅在十分必要时使用。V8 等优化的 JavaScript 引擎会努力回收被闭包困住的内存,不过我们还是建议在使用闭包时要谨慎。 ### 10.15 立即调用的函数表达式 314 > 立即调用的匿名函数又被称作立即调用的函数表达式(IIFE)。它类似于函数声明,但由于被包含在括号中,所以会被解释为函数表达式。紧跟在第一组括号后面的第二组括号会立即调用前面的函数表达式。使用 IIFE 可以模拟块级作用域,即在一个函数表达式内部声明变量,然后立即调用这个函数。 ```javascript // IIFE (function () { for (var i = 0; i < count; i++) { console.log(i); } })(); console.log(i); // 抛出错误 //在 ECMAScript 6 以后,IIFE 就没有那么必要了,因为块级作用域中的变量无须 IIFE 就可以实现同样的隔离 // 内嵌块级作用域 { let i; for (i = 0; i < count; i++) { console.log(i); } } console.log(i); // 抛出错误 // 循环的块级作用域 for (let i = 0; i < count; i++) { console.log(i); } console.log(i); // 抛出错误 ``` ### 10.16 私有变量 316 ##### 10.16.1 静态私有变量 ##### 10.16.2 模块模式 ##### 10.16.3 模块增强模式
顶部
收展
底部
[TOC]
目录
第1章 JavaScript简介
第2章 在 HTML中使用JavaScript
第3章 语言基础(1)语法变量
第3章 语言基础(2)数据类型
第3章 语言基础(3)操作符
第3章 语言基础(4)语句
第4章 变量、作用域与内存
第5章 基本引用类型
第6章 集合引用类型
第7章 迭代器与生成器
第8 章对象、类与面向对象编程
第9章 代理与反射
第10章 函数
第11章 期约与异步函数
第12章 BOM
第13章 客户端检测
第14章 DOM
第15章 DOM 扩展
第16章 DOM2 和 DOM3
第17章 事件
第18章 动画与 Canvas 图形
第19章 表单脚本
第20章 JavaScript API
第21章 错误处理与调试
第22章 处理 XML
第23章 JSON
第24章 网络请求与远程资源
第25章 客户端存储
第26章 模块
第27章 工作者线程
第28章 最佳实践
相关推荐
WebSocket