函数
一、定义
- 声明式
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] = 12
console.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) // 1
console.log(b); // not defind
var a = b = 123;
console.log(a) // 123
console.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); // 123 console.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对象内的i arr[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 function
console.log(fn) // undefined
// if 中 function 被预编译成表达式,赋值留在原地,所以立即执行函数AO中{fn:undefined}; 执行赋值,不执行不赋值
}());