JavaScript 中,函数实际上是一个对象。???? 声明JavaScript 用 function 关键字来声明一个函数:function fn () {}变体:函数表达式:var fn = function () {}这种没有函数名的函数被称为匿名函数表达式。???? return函数可以有返回值function fn () { return true}位于 return 之后的任何代码都不会执行:function fn () { return true console.log(false) // 永远不会执行}fn() // true没有 return 或者只写 return,函数将返回 undefined: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)}没有传值的命名参数,会被自动设置为 undefined// 参数有限function fn (a, b) { console.log(b) // undefined}fn(1)???? arguments函数可以通过内部属性 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通过 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,我觉得还是少用为好。???? this、 prototype具体查看总结:《关于 this 应该知道的几个点》《原型》???? 按值传递引用《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 = 1function 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 }}箭头函数的特点:没有 this,函数体内的 this 是定义时外部的 this不能被 new,因为没有 this不可以使用 arguments,可以使用 rest 代替不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。???? 参考《JavaScript 深入之参数按值传递》 by 冴羽《ECMAScript 6 入门》函数的扩展 - 箭头函数 by 阮一峰《JavaScript 高级程序设计》第三章 基本概念、第四章 4.1.3 传递参数、第四章 4.2 执行环境及作用域
...