函数
之前几节中围绕着函数梳理了 this、原型链、作用域链、闭包等内容,这一节梳理一下函数本身的一些特点。
javascript 中函数是一等公民。 并且函数也是对象,因为它们可以像任何其他对象一样具有属性和方法。它们与其他对象的区别在于函数可以被调用。简而言之,它们是 Function 对象。
一个函数是可以通过外部代码调用的一个“子程序”,函数内部包含了执行语句或表达式。每个自定义函数都是Function
的实例,并继承Function
的所有属性和方法,而Function
是语言本身提供的编程接口。
function foo (name) {
console.log(`hello, ${name}`)
}
var res = foo(`小明`) // 'hello, 小明'
console.log(res)
调用函数时,传递给函数的值被称为函数的实参(值传递),对应位置的函数参数名叫作形参。如果实参是一个包含原始值(数字,字符串,布尔值)的变量,则就算函数在内部改变了对应形参的值,返回后,该实参变量的值也不会改变。如果实参是一个对象引用,则对应形参会和该实参指向同一个对象。假如函数在内部改变了对应形参的值,返回后,实参指向的对象的值也会改.(欢迎查看参数传值一节)
如果函数内部没有通过return
返回一个值,则函数会默认返回undefined
.
函数在实际开发中承担着代码分块、 功能封装等任务。
函数定义
// 函数申明
foo()
let a = 1
function foo () {
console.log(a)
}
// 函数表达式 (函数变量)
let too = function (){
console.log('hello')
}
too()
// Function 构造函数实例化一个函数
let bar = new Function('console.log("hello")')
bar()
// 箭头函数
let fns = () => {
console.log('hello')
}
fns()
需要注意的是
函数申明
和
函数表达式
在变量提升时的区别,强烈建议阅读
变量对象
相关内容:
详情。同时函数申明的
函数名称
不能被改变,而函数表达式赋值给的变量可以被重新赋值。
函数的各种定义方式就不深入,这里需要注意的是通过使用构造函数(new Function()
)方式创建的函数,在每次调用的时候都会解析一次。所以不推荐使用 Function 构造函数创建函数, 因为它需要的函数体作为字符串可能会阻止一些 JS 引擎优化,也会引起浏览器资源回收等问题。
arguments
arguments
同this
一样是函数提供给函数内部使用的一个属性(ES6中箭头函数没有)。通过arguments
我们可以获取调用函数时传递进来的实参, 该属性是一个类数组对象,我们可以像数组一样进行数值的读取。
function foo () {
let a = arguments[0] // 获取函数的第一个参数
let b = arguments[1] // 获取函数的第二个餐宿
// 遍历函数的所有参数
for (let arg in arguments) {
console.log(`${arg}: ${arguments[arg]}`)
}
arguments[2] = 1122
}
foo(1, 2, 3)
虽然arguments
是类似于数组,但是除了通过索引获取元素和length
属性外,不能使用push
、pop
等方法。如果确实要进行修改操作,可以将其转换为真正的数组:
let args = Array.prototype.slice.call(arguments)
let args = [].slice.call(arguments)
// ES2015
let args = Array.from(arguments)
自执行函数
把函数定义和函数执行结合到一起就是立即执行函数,也叫自执行函数。
在官方术语中叫做 IIFE( 立即调用函数表达式),是在定义时就会立即执行的函数。被称为自执行匿名函数
的设计模式,主要包括两部分:
- 第一部分是包围在 圆括号运算符
()
里的一个匿名函数,这个匿名函数拥有独立的词法作用域。这不仅避免了外界访问此 IIFE 中的变量,而且又不会污染全局作用域。借用这个特性我们可以对局部功能进行封装,只暴露很少的方法给外部环境,这也是很多第三方库的封装方法之一。 - 第二部分再一次使用
()
创建了一个立即执行函数表达式,JavaScript 引擎到此将直接执行函数。
当函数变成立即执行的函数表达式
时,表达式中的变量不能从外部访问:
(function () {
var a = 1
})()
console.log(a) // a is not defined
将 IIFE 分配给一个变量,不是存储 IIFE 本身,而是存储 IIFE 执行后返回的结果:
let bar = (function (){
let a = 123
return a
})()
console.log(bar) // 123
别外自执行函数还有一些其他使用方式:
// 下面2个括弧()都会立即执行
(function(){ /* code */ }());
(function(){ /* code */ })();
// 由于括弧()和JS的&&,异或,逗号等操作符是在函数表达式和函数声明上消除歧义的
// 所以一旦解析器知道其中一个已经是表达式了,其它的也都默认为表达式了
var i = function(){ /* code */ }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();
// 如果你不在意返回值,或者不怕难以阅读
// 你甚至可以在function前面加一元操作符号
!function(){ /* code */ }();
~function(){ /* code */ }();
-function(){ /* code */ }();
+function(){ /* code */ }();
// 上面这种使用一元表达式这种方式其实是不太常见的
// 而且有时候肯定在一些场景下存在一些弊端,因为一元表达式会有一个不为undefined的返回值
// 要想返回值为undefined,那么最保险的就是使用void关键字
void function(){/* code */}();
()
左边必须是一个函数表达式,而不是一个函数申明。自执行函数也会用在模块封装的场景下。
闭包
函数和函数声明时的词法作用域形成闭包。我们可以使用闭包来保存变量、封装不需要暴露的私有属性和方法。
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
var foo = checkscope();
foo(); // "local scope"
彻底理解闭包的最好途径就是理解闭包的由来,闭包的产生和作用域链有密切关系。在作用域链一节已经梳理过闭包的原理。
函数式编程
未完成