你不知道的JS(上卷)笔记——作用域,作用域链,调用栈,闭包总结
作用域
1. 定义
可访问变量规则的集合。
2. 作用
- 解决命名冲突的问题。
- 隐藏私有属性。
3. 分类
1.1 按照作用的范围可以分为全局作用域和局部作用域(函数作用域)和块级作用域(由const和let等形成)。
1.2 eval和with等欺骗词法作用域的手段不是最佳实践。
4. 其他
- 立即执行函数,就像这样的形式:
(function f1() {
return 123;
})()
//或者
(function() {
return 123
}())
其中函数可以使具名函数也可以是匿名函数
使函数声明转变为函数表达式可以在 function 前面加上!,+,-,=等符号
-
提升
函数声明提升>变量提升>赋值操作
函数声明:以function开头声明函数的形式 而函数表达式可以通过下面这种形式定义函数
var f1 = function() {....}
-
全局对象
在浏览器中全局对象会挂靠早全局对象(window)中,而node中则挂在global中。 -
严格模式
"use strict" 要么放在整个js第一行才会生效要么在同级作用域(定义的地方而不是执行的地方)才会生效(记得加分号)。比如下面2中情况是有效的:
'use strict';
function foo() {
//'use strict';//或者写这里
console.log(this.a)
}
ps: 这里顺带说个js一个执行机制:
作用域链
自由变量:
定义:当我们访问一个在当前作用域没有申明的变量时,该变量就是一个自由变量。
作用域链:
定义: 当我们在当前作用域查找不到一个自由变量的定义时,我们会从其父级作用域查找,直到全局作用域。我们将这条查找的链路称为作用域链。
这里要注意的是js的作用域指的是词法作用域
也就是静态作用域
,即作用域在函数定义的时候就确定了,而不是执行的时候确定。
var scope = 'global scope'
function fn() {
var scope = 'local scope';
return function() {
console.log(scope)
}
}
fn()()//local scope
这里穿插一个有意思的操作符()
let obj = {
a: function() {
console.log(this.a)
}
};
obj.a();//[Function a]
(false || obj.a)();//undefined,因为()返回一个表达式,相当于,obj.a赋值给了圆括号。
console.log((1,2,3))//3。圆括号将最后一个表达式的值返回。
console.log([1,2,3][1,2])//3。相当于[1,2,3][(1,2)] => [1,2,3][2]
调用栈
JavaScript 是解释型语言。js的执行分为解释和执行阶段:
解释阶段:
- 词法分析
- 语法分析
- 作用域规则确定
执行阶段:
- 创建执行上下文
- 执行函数代码
- 垃圾回收(引用计数和标记清除)
解释阶段我们前面讲过了,这里说下执行阶段创建上下文的时候就会涉及到调用栈
。这里注意一下顺序:
确定作用域 => 创建执行上下文=>执行函数代码
结合具体例子:
var scope = "global scope";
function checkscope(){
var scope2 = 'local scope';
return scope2;
}
checkscope();
// 1. 创建函数 checkscope,保存作用域链到内部[[scope]]
// checkscope.[[scope]] = [
// globalContext
// ]
// 2. 创建checkscope的执行上下文,这个执行上下文是一个对象,里面包含作用域链(scope), 活动对象(active-object);而活动对象是用arguments创建的。
// checkscope-context = {
// ['active-object']: {
// arguments: {
// length: 0
// },
// scope2: undefined
// }
// [[scope]]: [ checkscope-context['active-object'], checkscope.[[scope]]]
// }
// 3. 将创建的checkscope执行上下文压入js上下文执行栈。
// EsStack = [
// checkscope-context,
// global-context
// ]
// 4. 开始执行js上下文执行栈
// checkscope-context = {
// ['active-object']: {
// arguments: {
// length: 0
// },
// scope2: 'local scope';
// }
// [[scope]]: [ checkscope-context['active-object'], checkscope.[[scope]]]
// }
// 5. 函数出栈(垃圾回收)
// EsStack = [
// global-context
// ]
image
闭包
1. 定义
当一个函数的词法作用域覆盖了另外一个函数的词法作用域的时候就形成了闭包,闭包是一种状态。
简而言之就是一个函数可以读取另外一个函数内部变量的状态就是闭包。
2. 作用(优点):
- 封装私有属性
- 变量常驻内存,随时访问(比如用于构造模块)
- 避免命名冲突
3. 缺点:
- 消耗内存
- 容易造成内存泄漏(比如在mvvm框架构建的单页应用中如果在某个页面初始化了一个第三方库比如jquery,如果在组件的生命周期的销毁阶段没有销毁初始化的第三方库 就造成了内存泄漏导致页面变卡)
下面的一段代码内存泄漏和误修改父函数内部变量的一个例子(可以加深理解)
(function f1(window) {
var a = 1;
function f2() {
a = a + 1;
console.log(a)
}
function f3() {
a = a - 1;
console.log(a)
}
window.f2 = f2;
window.f3 = f3;
})(window)
setInterval(function() {
f2()
}, 1000)
setInterval(function() {
f3()
}, 1000)
function f1(window) {
var a = 1;
function f2() {
a = a + 1;
console.log(a)
}
function f3() {
a = a - 1;
console.log(a)
}
return {
f2,
f3
}
}
let f2 = f1().f2; //新分配了一片内存
let f3 = f1().f3 //新分配了一片内存
setInterval(function() {
f2()
}, 1000)
setInterval(function() {
f3()
}, 1000)
第一段代码中,f2和f3分别都对内存中同一个a进行了操作,
第二段代码中, f2,f3声明时同时分配了一片一样的内存,但是实际上我们可能不需要。