作用域
先认识全局变量和局部变量
- 局部变量:在函数内部定义的变量,只在函数内部起作用,函数执行结束,变量会自动删除
即在一个函数内部定义的变量,只在本函数范围内有效
- 全局变量:“在函数外定义的变量”,即从定义变量的位置到本源文件结束都有效
即可以被程序所有对象或者函数引用
再认识何为“提升”
我们首先要知道,Javascript在执行前会对整个文件的声明部分做完整分析,包括局部变量,但是不能对变量定义做提前解析。(就是提前知道有这变量,但是不看这变量的值)
未提升:
console.log(a);// a is not defined
a=1;
提升后:
console.log(a);// undefined(声明被提升但没有提升初始值)
var a=1;
函数也是如此:
var fun=function(){
console.log("asd");
}
fun();//asd
fun变量声明被提升了,但是初始化值(函数内容)未被提升
fun();
var fun=function(){
console.log("asd");//fun is not a function
}
最后认识一下this
this对象指向根据函数的执行环境绑定
情况1. 当函数被作为某个对象的方法调用时,this指向那个对象(上一层)
情况2. this等于最后调用函数的对象
举个栗子:

this.name=window.name,所以输出为window.name=kiki。
第二个this所属的函数是对象person的方法,被调用时,this指向含有他的对象,也就是上一层(person),所以调用其实this.name=person.name,输出为person.name=chuchu。
作用域
作用域是指代码中特定部分中变量函数和对象的访问性。简单说就是作用域规定了各个部分的作用范围。
举个栗子:
function print(){
var a=3;
}
console.log(a);// a is not defined
由于是在在全局作用域中使用变量a,而全局变量并未声明变量a,所以并没有控制台输出结果。一个个作用域可以视为一块块独立的模块,变量可以在模块内自由调用,但是一旦在该作用域之外便无法直接调用,作用域的最大用处就是隔离变量,不同作用域下同名变量不会有冲突。
全局作用域
类似C语言的全局变量,在代码中任何地方都能访问到的对象拥有全局作用域
- 最外层函数 和在最外层函数外面定义的变量拥有全局作用域
var a=3;
function printOut(){
var b=33;
function printIn(){
var c=333;
console.log(c);
}
printIn();
}
console.log(a);
console.log(b);//not defined
printIn();//not defined
- 变量a作为全局变量,拥有全局作用域,因此在代码任何位置是被声明过。
- 变量b作为函数 printOut() 内的变量,作用域限于该函数内,对外部来说并未声明。
- 同理,函数printIn() 是在函数 printOut内声明的,因此对最外层来说也没有声明
- 所有末定义直接赋值的变量自动声明为拥有全局作用域
var a=3;
function printOut(){
b=33;
}
printOut();
console.log(a);
console.log(b);
a的作用域与上文同理,需要注意的是:如果未定义直接赋值的变量拥有全局作用域,需要调用printOut() 函数,让主函数知道有b的存在,然后才能使之默认成拥有全局作用域的变量
- 所有window对象的属性拥有全局作用域
简单来说就是window对象的内置属性拥有全局作用域,是最外层的声明。
函数作用域
指声明在函数内部的变量,一般只在特定的代码片段中能被访问,一般出现在函数内部并作用于函数内部。
栗子1:
function print(){
var a=3;
function printIn(){
console.log(a);
}
}
console.log(a);// not defined
printIn();// not defined
内层作用域可以访问外层作用域的变量
做一个不是很严谨的比喻,作用域就像我国各地的法院,各市法院处理本市案件,不会越权干涉其他市法院办案,但是如果有市法院无法处理的信息,便通报省法院寻求帮助,若省法院无法处理则向更高机关求助...
栗子2:
add = function (){
var a = 10;
a++;
console.log(a);
}
for(let i=0;i<3;i++)
{
add();
}
//11 11 11
函数中没有用的局部变量就会被销毁内存,在你每次函数执行完毕后,你的这个a就被当成没有用的变量被回收了,或者换句话说,内存被销毁了。所以你的a始终都是初始的那个10。
而如果a作为全局变量定义在函数外,只有当你关闭页面or浏览器的时候才会被销毁。所以那时候a仿佛就有了存储功能,能够记录每次变化后的值,无论运行与否,a一直保持着上一次++后的值。
块级作用域
大括号“{}”中间的语句被称为块语句 ,如 if 和 switch 条件语句或 for 和 while 循环语句,不会创建一个新的作用域。在块语句中定义的变量将保留在它们已经存在的作用域中。
概念
通过let和const声明在一个块语句中,变量在块外无法被访问。通常被创建在一个函数/一个块语句内部。
在函数里声明的变量在外边是不能用的、而块级是可以的
特点
- 声明变量不会提升到代码块顶部
function kiki() {
var a=1;
if(a)
{
let b=10;
var c=20;
}
console.log(c);
console.log(b);
}
kiki();
//20
//b is not defined
此处var声明的c可以在函数kiki() 内被调用,但是let只能在if的大括号内调用,let让变量的作用域范围进一步缩小
- 禁止重复声明
一个变量被var申明后进制let声明,否则报错,但如果在嵌套的作用域内使用let声明一个同名的新变量,则不会报错:
var a = 30;
if (1)
{
let a = 40;
}
- 循环中的绑定块作用域
可用于for循环内进行计数
for(let i=0;i<10;i++)
可以避免变量污染
(1)当使用let声明循环变量
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[3](); // 10
a[8](); // 10
变量i是var声明的,为全局变量作用域,在整个for循环中,每次循环中当i发生改变时,a中每个 i 值也是同步改变。
由于a数组是由i一个一个声明的,因此当i=0时,a[0]内存为0,当i=1时,a[i]的所有元素都为a[i](即1)
当i=0时 a: [ a[0] ] =>> a=[0]
当i=1时 a: [ a[1],a[1]] =>> a=[1,1]
当i=2时 a: [ a[2],a[2],a[2] ] =>> a=[2,2,2]
(2)当使用let声明循环变量
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
当前的i只在本次循环有效,每次循环i都是新变量,不会干扰之前赋值的a[i]。
作用域链
自由变量
当前作用域没有定义的变量称为自由变量。 自由变量可以通过向上层(父级作用域寻找)
var a = 10;
function fn(){
var b=20;
console.log(a);// 10
console.log(b);
}
作用域链
如果父级也没有,就一层一层向上寻找,直到找到全局作用域还是没找到,就宣布没有声明。
这种一层一层的关系,就是作用域链 。
- 作用域链的前端,始终都是当前执行的代码所在环境的变量对象
- 作用域链中的下一个对象来自于外部环境,而在下一个变量对象则来自下一个外部环境,一直到全局执行环境
- 全局执行环境的变量对象始终都是作用域链上的最后一个对象
举个栗子1:
function foo() {
let a = 1;
function bar() {
let a = 2;
function baz() {
let a = 3;
console.log(a);
}
baz();
}
bar();
}
foo();
baz 的 scope: [baz,bar,foo,window]
bar 的 scope: [bar,foo,window]
foo 的 scope: [foo,window]
举个栗子2:
let a = 1;
function foo(){
let a = 2;
function baz(){
console.log(a);
}
bar(baz);//
}
function bar(fn){
let a = 3;
fn();//
}
foo();
foo 的 scope: [foo,window]
bar 的 scope: [bar,foo,window]
baz 的 scope: [baz,bar,foo,window]
闭包
认识闭包
function foo(){
var a = 1;
function bar(){
console.log(a);
}
}
对于这个函数,我想在foo()外部调用bar()函数,但是从上面所学作用域内容来看是不可以的,这时候我们需要使用闭包来保存函数内的函数或变量
优点:
- 闭包可以形成独立的空间,永久的保存局部变量。
- 避免全局变量污染
- 变量长期在内存中
缺点:
- 内存泄露
- 性能降低
用处:
- 在外部操作函数内部变量
- 变量始终保持在内存中,不会函数执行后就被销毁
栗子:
function foo(){
var a = 1;
function bar(){
console.log(a);
}
return bar;
}
var baz = foo();
将bar()作为返回值从foo()中返回,这样就可以在使用foo()时调用内部的bar()
分析几个闭包
- 代码一:

在函数math() 中包含了一个add() 函数,并将其作为返回值返回。因为在math() 函数被调用结束后,math() 的局部上下文就会被销毁,所以虽然是将math() 赋给变量adder() ,其实是将add() 连同内部的函数求和部分,赋给变量adder() 。
这时,原本值为undefined的变量adder() 便拥有了函数意义(变成函数)。
- 代码二:
function createCounter() {
let counter = 0
const myFunction = function() {
counter = counter + 1
return counter
}
return myFunction
}
const increment = createCounter()
const c1 = increment()
const c2 = increment()
const c3 = increment()
console.log('example increment', c1, c2, c3)
将myFunction() 函数作为返回值通过createCounter() 返回,下面调用createCounter() 的时候,会调用内部的myFunction() 函数。
- 在c1调用的时候,开始执行myFunction() 函数,寻找变量counter,在闭包createCounter() 内有counter变量,其值为0,在myFunction() 函数结束后返回counter的值到c1。
- 在c2调用的时候,由于闭包的作用,内部counter变量的值还保留为1,这时myFunction() 函数结束后返回counter=2到c2。
- c3同上
- 代码三:
let c = 4
function addX(x) {
return function(n) {
return n + x
}
}
const addThree = addX(3)
let d = addThree(c)
console.log('example partial application', d)
addX() 函数可以接受参数x并返回另一个函数。他所返回的函数还接受一个参数并将其添加到变量x中。
开始调用时,addX(3)意为将x=3传入addX()函数并复制给内函数中的x,并返回内函数的接口。
变量addThree也拥有函数可以接受参数的能力,传入c=4进入函数addThree(),这时c=4进入并赋值给n。
变量d接受了函数addThree()最后的结果:内函数所返回的3+4=7
销毁闭包
类似《寻梦环游记》的设定: 一个人真正的死亡,是被所有的人遗忘。
当再也没有代码需要这个函数的时候,他和他内部的变量都会被销毁掉。
只要代码中不再保存这个函数的引用了,这个函数和函数所形成的闭包也就会被一并销毁