函数
一、定义
- 声明式
functon fn(){}
- 表达式
var fn = function(){}
(函数表达式一般指 匿名函数)
二、参数(形参,实参)
arguments
类数组对象 表示函数的实参集合
arguments.callee
—指向当前执行的函数
arguments.length
—指向传递给当前函数的参数数量
arguments.arguments
— 返回一个新的Array
迭代器对象,该对象包含参数中每个索引的值
示例:
function test(a,b,c){// 获取实参 和实参相互映射, length与实参个数相对应// 不在严格模式下:// arguments[0]改变,会使相对应的形参也发生改变,修改形参,arguments[0]也会相应被修改// 而在严格模式则不会被修改"use strict"arguments[0] = 12console.log(a) }test(1,2,3);
- 函数
.length
– 表示函数期望的参数数量
function nu(a,b,c,d){console.log(nu.length) // 4// 表示期望传入参数的个数,和实际传入参数的个数无关}nu(1,2)
三、预编译(执行期间上下文)
- 预编译前了解
- 暗示全局变量
imply global
: 即任何变量如果变量未声明赋值,此变量就为全局对象window
所有 - 全局上的任何变量都归全局对象
window
所有
window
就是全局的域
window == GO对象
- 暗示全局变量
- 预编译 步骤
function
预编译- 创建
AO
对象 (Active Object
) - 形参,声明变量为
AO
对象 属性,值为undefinded
- 实参的值与形参的值统一
- 找函数声明,存入
AO
对象中,函数名为属性名,函数体为值
- 创建
- 全局预编译
- 生成 GO 对象
- 变量声明 为 GO 对象属性, 值为
undefinded
- 函数声明 存入 GO 对象中
示例:
// 一、预解析
// GO{
// 先找声明变量,未找到,执行时b赋值123
// b:123
// },
function a(a) {console.log(a) // 1console.log(b); // not defindvar a = b = 123;console.log(a) // 123console.log(b) // 123
}
a(1)
// AO{
// a:undefined->1
// }
四、作用域、作用域链
-
[[scope]]
:每个javascript函数都是一个对象,每个对象都有属性,有的属性能访问,有的属性不可以,这些属性仅供javascript
引擎存取,[[scope]]
就是其中一个。[[scope]]
就是我们所说的作用域,其中存储了执行期上下文的集合作用域链 :
[[scope]]
中存储的执行期上下文对象的集合,呈链式链接,我们把这种链式链接叫做作用域链。示例:
function a() {function b() {var b = 1;aa = 123;global = '局部'function c() { } // 执行b 才会定义 c; 不执行函数,永远不读取函数内部}var aa = 444;b();console.log(aa); // 123console.log(global); // 局部 } var global = '全局'; a(); //1. a 定义 a.[[scope]] ---->0: GO{a:function} // 所谓定义就是执行前定义 // 2.a 执行 a.[[scope]] --- > 0: AO{b:function} a 的 AO对象 // ----> 1: GO{} // 3. b 定义 b.[[scope]] -------> 0: AO{ b:function } // -------> 1:GO{} // 4. b 执行 b.[[scope]] ---------> 0: AO{c:function} b 的AO对象 // ----------->1: AO{b:function} a 的AO 对象 // -----------> 2: GO{} a 的 GO 对象
-
立即执行函数 一般设置初始值或者执行一次;只有表达式才能执行
(function(){}());(function(){})();+ function(){}();- function(){}();... || function(){}(); ... && function(){}()
示例:实现索引
// 第一种情况: function a() {var arr = [];for (var i = 0; i < 5; i++) {// 一直在替换AO对象内的iarr[i] = function () {console.log(i)}};return arr; } var test = a(); for (var j = 0; j < 5; j++) {test[j](); } // 输出:5,5,5,5,5// 第二种情况: function a() {var arr = [];for (var i = 0; i < 5; i++) {(function (j) {arr[j] = function () { // 定义fun时站立于立即执行函数的AO对象内console.log(j); // 读取的是AO对象内的j}}(i))};return arr; } var test = a(); for (var j = 0; j < 5; j++) {test[j](); } // 输出 0,1,2,3,4
五.闭包
- 什么是闭包
简单来说,闭包就是一个内部函数能够访问和操作其外部函数变量的函数
当内部函数保存到外部时,将会生成闭包
示例:
function a() {var aa = []function b() {var bb = 'bbbbb参数'aa = function () {console.log(bb)}};b();return aa;
};
var test = a();
test();
// a 定义 a.[[scope]] ----->GO{}
// a 执行 a.[[scope]] ---->0: a AO{aa: []],b()}
// ----->1: GO{ }
// b 定义 b.[[scope]] ---->0: a AO{aa: undefined,b()}
// ----->1: GO{ }
// b 执行 b.[[scope]] ---->0:bAO{bb:...}
// ---->1: a AO{aa: [function()],b()}
// ----->2: GO{ }
- 闭包的作用
闭包有两个主要的功能:保存和保护。
- 保存功能使得闭包能够记住并访问其词法作用域,即使该函数在其词法作用域之外执行。
- 保护功能则允许闭包对变量进行私有封装,防止变量被外部随意修改,同时也防止了命名空间的污染。
- 用途:
- 实现共有变量
- 可以做缓存
- 可以实现封装,属性私有化,私有化变量
- 模块化开发,防止污染全局变量
示例一:简单的计数器, 实现共有变量
function createCounter() { var count = 0; // 私有变量 return { increment: function() { // 公开方法,用于增加计数器 count++; }, getCount: function() { // 公开方法,用于获取计数器的值 return count; } };
} // 使用createCounter函数创建一个计数器实例
const counter = createCounter(); // 调用公开方法来增加计数器的值
counter.increment();
counter.increment(); // 获取计数器的值
console.log(counter.getCount()); // 输出:2
示例二:实现缓存
function A() {var num = 1;function B() {num++;console.log(num);}function C() {num--;console.log(num);}return [B, C]
}
var arr = A();
arr[0](); // 2
arr[1](); // 1
- 闭包的缺点
闭包可能导致内存泄漏的问题,因为闭包可以保留其外部环境的引用,如果这部分环境很大或者生命周期很长,就可能导致不必要的内存占用。
注意:
if
条件句中的function
会被编译成 函数表达式, 声明会被提升到当前作用域的最顶部, 但是赋值会被留在原地
示例:
function fn() {console.log('哈哈哈');
}
(function () {if (false) {function fn() {console.log('啦啦啦');}}fn(); // fn is not a functionconsole.log(fn) // undefined// if 中 function 被预编译成表达式,赋值留在原地,所以立即执行函数AO中{fn:undefined}; 执行赋值,不执行不赋值
}());