在 JavaScript 中,函数其实是一个对象。前端
JavaScript 用 function 关键字来声明一个函数:git
function fn () {
}
复制代码
变体:函数表达式:es6
var fn = function () {
}
复制代码
这种没有函数名的函数被称为匿名函数表达式。github
函数能够有返回值数组
function fn () {
return true
}
复制代码
位于 return 以后的任何代码都不会执行:浏览器
function fn () {
return true
console.log(false) // 永远不会执行
}
fn() // true
复制代码
没有 return 或者只写 return,函数将返回 undefined:bash
function fn () {
}
fn() // undefined
// 或者
function fn () {
return
}
fn() // undefined
复制代码
函数能够带有限个数或者不限个数的参数闭包
// 参数有限
function fn (a, b) {
console.log(a, b)
}
// 参数不限
function fn (a, b, ..., argN) {
console.log(a, b, ..., argN)
}
复制代码
没有传值的命名参数,会被自动设置为 undefinedapp
// 参数有限
function fn (a, b) {
console.log(b) // undefined
}
fn(1)
复制代码
函数能够经过内部属性 arguments 这个类数组的对象来访问参数,即使没有命名参数:函数
// 有命名参数
function fn (a, b) {
console.log(arguments.length) // 2
}
fn(1, 2)
// 无命名参数
function fn () {
console.log(arguments[0], arguments[1], arguments[2]) // 1, 2, 3
}
fn(1, 2, 3)
复制代码
arguments 的长度由传入的参数决定,并非定义函数时决定的。
function fn () {
console.log(arguments.length) // 3
}
fn(1, 2, 3)
复制代码
若是按定义函数是决定个的,那么此时的 arguments.length 应该为 0 而不为 3。
arguments 对象中的值会自动反应到对应的命名参数,能够理解为同步,不过并非由于它们读取了相同的内存空间,而只是保持值同步而已。
function fn (a) {
console.log(arguments[0]) // 1
a = 2
console.log(arguments[0]) // 2
arguments[0] = 3
console.log(a) // 3
}
fn(1)
复制代码
严格模式下,重写 arguments 的值会致使错误。
经过 callee 这个指针访问拥有这个 arguments 对象的函数
function fn () {
console.log(arguments.callee) // fn
}
fn()
复制代码
长的跟数组同样,能够经过下标访问,如 arguments[0],却没法使用数组的内置方法,如 forEach 等:
function fn () {
console.log(arguments[0], arguments[1]) // 1, 2
console.log(arguments.forEach) // undefined
}
fn(1, 2)
复制代码
经过对象那章知道,能够用 call 或者 apply 借用函数,因此 arguments 能够借用数组的内置方法:
function fn () {
Array.prototype.forEach.call(arguments, function (item) {
console.log(item)
})
}
fn(1, 2)
// 1
// 2
复制代码
对于如此诡异的 arguments,我以为仍是少用为好。
具体查看总结:
引用《JavaScript 高级程序设计》4.1.3 的一句话:
ECMAScript 中全部函数的参数都是按值传递的,也就是说,把函数外部的值复制给函数内部的参数,就和把一个变量复制到另外一个变量同样。
基本类型的传递很好理解,就是把变量复制给函数的参数,变量和参数是彻底独立的两个个体:
var name = 'jon'
function fn (a) {
a = 'karon'
console.log('a: ', a) // a: karon
}
fn(name)
console.log('name: ', name) // name: jon
复制代码
用表格模拟过程:
栈内存 | 堆内存 | |
name, a | jon |
将 a 复制为其余值后:
栈内存 | 堆内存 | |
name | jon | |
a | karon |
var obj = {
name: 'jon'
}
function fn (a) {
a.name = 'karon'
console.log('a: ', a) // a: { name: 'karon' }
}
fn(obj)
console.log(obj) // name: { name: 'karon' }
复制代码
嗯?说好的按值传递呢?咱们尝试把 a 赋值为其余值,看看会不会改变了 obj 的值:
var obj = {
name: 'jon'
}
function fn (a) {
a = 'karon'
console.log('a: ', a) // a: karon
}
fn(obj)
console.log(obj) // name: { name: 'jon' }
复制代码
参数 a 只是复制了 obj 的引用,因此 a 能找到对象 obj,天然能对其进行操做。一旦 a 赋值为其余属性了,obj 也不会改变什么。
用表格模拟过程:
栈内存 | 堆内存 | |
obj, a | 引用值 | { name: 'jon' } |
参数 a 只是 复制了 obj 的引用,因此 a 能找到存在堆内存中的对象,因此 a 能对堆内存中的对象进行修改后:
栈内存 | 堆内存 | |
obj, a | 引用值 | { name: 'karon' } |
将 a 复制为其余值后:
栈内存 | 堆内存 | |
obj | 引用值 | { name: 'karon' } |
a | 'karon' |
所以,基本类型和引用类型的参数传递也是按值传递的
理解做用域链以前,咱们须要理解执行环境 和 变量对象。
执行环境定义了变量或者函数有权访问的其它数据,能够把执行环境理解为一个大管家。
执行环境分为全局执行环境和函数执行环境,全局执行环境被认为是 window 对象。而函数的执行环境则是由函数建立的。
每当一个函数被执行,就会被推入一个环境栈中,执行完就会被推出,环境栈最底下一直是全局执行环境,只有当关闭网页或者推出浏览器,全局执行环境才会被摧毁。
每一个执行环境都有一个变量对象,存放着环境中定义的全部变量和函数,是做用域链造成的前置条件。但咱们没法直接使用这个变量对象,该对象主要是给 JS 引擎使用的。具体能够查看《JS 总结之变量对象》。
而做用域链属于执行环境的一个变量,做用域链收集着全部有序的变量对象,函数执行环境中函数自身的变量对象(此时称为活动对象)放置在做用域链的最前端,如:
scope: [函数自身的变量对象,变量对象1,变量对象2,..., 全局执行环境的变量对象]
复制代码
做用域链保证了对执行环境有权访问的全部变量和函数的有序访问。
var a = 1
function fn1 () {
var b = 2
console.log(a,b) // 1, 2
function fn2 () {
var c = 3
console.log(a, b, c) // 1, 2, 3
}
fn2()
}
fn1()
复制代码
对于 fn2 来讲,做用域链为: fn2 执行环境、fn1 执行环境 和 全局执行环境 的变量对象(全部变量和函数)。
对于 fn1 来讲,做用域链为: fn1 执行环境 和 全局执行环境 的变量对象(全部变量和函数)。
总结为一句:函数内部能访问到函数外部的值,函数外部没法范围到函数内部的值。引出了闭包的概念,查看总结:《JS 总结之闭包》
ES6 新语法,使用 => 定义一个函数:
let fn = () => {}
复制代码
当只有一个参数的时候,能够省略括号:
let fn = a => {}
复制代码
当只有一个返回值没有其余语句时,能够省略大括号:
let fn = a => a
// 等同于
let fn = function (a) {
return a
}
复制代码
返回对象而且没有其余语句的时候,大括号须要括号包裹起来,由于 js 引擎认为大括号是代码块:
let fn = a => ({ name: a })
// 等同于
let fn = function (a) {
return { name: a }
}
复制代码
箭头函数的特色: