易混
- 函数声明(function statement)
使用function关键字声明一个函数,再指定一个函数名,叫函数声明
如:function fn(){}; - 匿名函数 ( anonymous functions )
使用function关键字声明一个函数,但是没有命名,这个函数就叫匿名函数
如:function(){}; - 函数表达式( function expression )(赋值操作-函数没有声明提升)
把一个匿名函数当做值传给一个变量,叫函数表达式,是最常见的函数表达式语法
如:let fn = function(){};
变量名解析顺序
同一作用域下,相同变量名,后面的赋值会覆盖前面的;
同一作用域下,如果变量名和函数名相同,那么函数名会覆盖变量名,不论它们在代码的顺序如何;但是它们的初始化赋值是按顺序执行的;
函数
1,函数创建
- 开辟一个堆内存
- 把函数体中的代码当做字符串存储进去
- 把堆内存的存储地址赋值给函数名/变量名
- 函数在哪创建,那么他执行的时候所需要查找的上级作用域就是谁
2,函数的执行
- 形成一个全新的私有的作用域 / 执行上下文 / 私有栈内存(执行一次产生一个,多个之间也不会产生影响)
- 形参赋值,变量提升
- 代码执行(把所属堆内存中的代码字符串拿出来一行行执行)
- 遇到一个变量,首先判断他是不是私有变量(形参和在私有作用域中声明的变量就是私有变量),是私有的变量就操作自己的变量即可;不是私有的就向上级作用域查找一直找到全局作用域为止 ==>作用域链查找机制
- 私有变量和外界变量没有必然关系,可以理解为被私有栈内存保护起来了,这种机制就是闭包的保护机制
3,关于堆栈内存释放问题
函数执行就会形成栈内存(从内存中分配的一块空间),如果内存都不销毁释放,就很容易造成栈内存溢出(内存爆满,电脑卡死)
- 堆内存释放问题
堆内存产生 ==>创建一个引用类型的值,就会产生一个堆内存
堆内存释放 ==>如果当前创建的堆内存不被其他东西占用了(浏览器会在空闲的时候,查找每一个内存的引用状况,不被占用的都会给回收释放掉),则会释放
let obj = {name:'js'};
let oop = obj;
//此时obj和oop都占用着对象的堆内存,想要释放对象的堆内存,需要手动解除变量和值的关联(null:空对象指针)
obj=null;
oop=null
- 栈内存释放问题
栈内存产生 ==>
1,打开浏览器形成的全局作用域
2,执行函数形成的私有作用域
3,ES6中let/const形成的块级作用域都是栈内存
4,.....
栈内存释放 ==>
全局栈内存:关掉页面的时候才会销毁掉
私有栈内存:
1,一般函数只要执行完成,形成的私有栈内存就会被销毁释放掉(排除死递归,死循环模式)
function fn(){//...}
fn();//函数执行形成栈,执行完成栈内存销毁
2,但是一旦栈内存中的某个东西(一般堆地址),被私有作用域以外的事物给占用了,则当前私有栈内存不能立即被释放销毁
(特点:私有作用域中的私有变量等信息也保留了下来)
function fn(){
return function(){
//...
}
}
let f = fn();//f占用了fn执行形成的栈内存中的一个东西(堆地址),则fn形成的栈内存不能立即被释放掉了
闭包
1,函数执行形成不被释放的私有栈内存,保护里边的变量不被外界干扰(外边修改不了私有的,私有的也无法修改外边的)
2,形成不销毁的栈内存,里面的私有变量保存了下来
闭包题1:
var i = 5;
function fn(i){
return function(n){
console.log(n + (++i))
}
}
var f = fn(1); //fn(1)不销毁
f(2); //4 2 + ++1(1是上级作用域中的i) ==>fn(1)中的i进行++操作,1变为2 f(2)销毁
fn(3)(4);//8
fn(5)(6);//12 fn执行形成新的栈内存与之前执行不会产生影响
f(7);//10 f执行的上级作用域也是fn(1),在此之前i已经进行了++操作
console.log(i); //全局5
//解题: 闭包
/*
var f = fn(1) //将fn(1)执行后的结果赋值给f
1.f(2) n=2 n+(++i) i为fn的形参结果是1 所以++i的结果结果就是2, 最后结果=>4
2.fn(3)(4) 函数执行->全新的栈内存 4+(++3) =>8
3.fn(5)(6) 函数执行->全新的栈内存 6+(++5) =>12
4.f(7) 7+(++2)=>10 i的值是f(2)执行后进行++操作后的值
5.i的值还是5
*/
闭包题2:
var i = 10
function fn(){ //
i -= 2;
return function(n){ //
console.log((++i)-n)
}
}
var f = fn(); //全局i = 8->9-10
f(1);//8
f(2);//8
fn()(3);//6//fn执行全新栈内存 全局i由10又变为8->9
fn()(4);//4//fn执行全新栈内存 全局i由9又变为7->8
f(5);//8->9 - 5 = 4
console.log(i);//9
// 整个解题思路:
/*
函数内部return一个函数-->闭包->保护内部变量
函数中的i是全局的,所以将fn()执行后全局的i变为8 f为return后的函数 小f执行只操作函数内部的代码
1.然后执行f(1) 没有形参赋值,提前声明全局下面的8进行++操作变为9, 所以f(1)的结果是9-1=8
2.执行f(2) 全局下的i由9变为10 结果为10-2=8
3.fn()(3)首先新的函数执行,形成新的栈内存,fn()先执行全局i由10进行-2操作变为8,然继续执行(3)全局i进行++变为9 结果9-3=6
4.fn()(4)新的函数执行,形成新的栈内存,fn()执行全局i由9变为7,继续执行全局下的i由7变为8 结果 8-4=4
5.f(5)执行的是return后面的函数,全局i值为8,然后进行++操作变为9,最后结果是9-5=4
6.最后的全局i就是4
*/
作用域与上下文
作用域(scope) 是指变量的可访问性,上下文(context)是指 this 在同一作用域内的值。特别需要注意的是,执行期上下文中的上下文这个词语是指作用域而不是上下文
函数的三种角色
构造函数 Function的实例对象 普通函数
易混
- 函数声明优于变量声明
function a(){}
var a;
console.log(a);//a(){}
var a = 1;
function a() {}
alert(a);//1
相当于:
a = function() {}
var a;//没意义
a = 1; // 重新赋值
alert(a);//1
function f(){
n = 10
}
f()
console.log(window.n);//10
- 案例
1.
var n = 1;
fn(n);
function fn() {
console.log(n);//undefined
var n = 2;
} //=> fn执行没有形参赋值,只有变量提升
var n = 1;
fn(n);
function fn(n) {
console.log(n);// 1
var n = 2;
} //=>新参赋值 n=1var声明的n变量提升,由于已经存在n,所以不会重新定义变量
var n = 1;
fn(n);
function fn(n) {
console.log(n); //f n(){}
function n() {}
var n = 2;
}
//=> 执行顺序
n = 1
n = function(){}//将1替换
var n //没有意义
- 总结
1. 当变量和函数重名的时候,函数的优先级高于变量。
2. 函数体内部,局部变量(函数内部声明过的)的优先级比同名的全局变量高。
3. 未使用var关键字定义的变量都是全局变量。全局变量都是window对象的属性
4. 预解析的顺序是从上到下,函数的优先级高于变量,函数声明提前到当前作用域最顶端,在var之上。
如果有形参,就先给形参赋值
5. 预解析变量是只声明,不赋值,默认为undefined。函数声明加定义
6. '变量重名时,不会重新声明,但是执行代码的时候会重新赋值。'
7. 函数重名时,后者会覆盖前者。
VO/AO
变量对象:Variable Object, VO
激活对象:AO