# (三)作用域
作用域的篇幅不会太长,作为自己对Js总结的第三篇文章,主要是承上启下。 之后会涉及到执行上下文,闭包等相关专题,为了避免内容过多,作用域这一部分单独总结。
# 目录
前言
JavaScript内功系列:
- this指向详解,思维脑图与代码的结合,让你一篇搞懂this、call、apply。系列(一) (opens new window)
- 从原型到原型链,修炼JavaScript内功这篇文章真的不能错过!系列(二) (opens new window)
- 本文
一、作用域的定义
一张导图概括本节内容 1.1 常见的解释
- 一段程序代码中所用到的名字并不总是有效,而限定它的可用性的范围就是这个名字的作用域;
- 作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限;
- 通俗的讲作用域就是一套规则,用于确定在何处以及如何查找某个变量的规则
function func(){
var a = 100;
console.log(a); // 100
}
console.log(a) // a is not defined a变量并不是任何地方都可以被找到的
1.2 JavaScript中作用域工作模型
JavaScript 采用是词法作用域(lexical scoping),也就是静态作用域:
- 函数的作用域在函数定义的时候就决定了
与之对应的还有一个动态作用域:
- 函数的作用域是在函数调用的时候才决定的;
1.3 全局变量和局部变量
根据定义变量的方式又可以分为:
局部变量:只能在函数中访问,该函数外不可访问;
- 定义在函数中的变量
function fn(){
var name = '余光';
console.log(name);
}
console.log(name); // ?
fn(); // ?
全局:任何地方都能访问到的对象拥有全局作用域。
- 函数外定义的变量
- 所有末定义直接赋值的变量自动声明为拥有全局作用域
var a = 100;
console.log('a1-',a);
function fn(){
a = 1000;
console.log('a2-',a);
}
console.log('a3-',a);
fn();
console.log('a4-',a);
注意:在ES6之后又提出了块级作用域,它们之间的区别我们之后再来讨论。
二、理解作用域
根据第一节的描述,我们一一验证一下
2.1 理解词法作用域
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
bar();
我们结合定义去分析:
- 执行
bar
函数,函数内部形成了局部作用域; - 声明value变量,并赋值2
- 执行
foo
函数,函数foo的作用域内没有value
这个变量,它会向外查找 - 根据词法作用域的规则,函数定义时,
foo
的外部作用域为全局作用域 - 打印结果是1
如果是动态作用域的话:结果就是2,不知道你是否想明白了?
2.2 全局变量
var str = '全局变量';
function func(){
console.log(str+1);
function childFn(){
console.log(str+2);
function fn(){
console.log(str+3);
};
fn();
};
childFn();
}
func();
// 全局变量1
// 全局变量2
// 全局变量3
再来分析下面的代码:
var a = 100;
function fn(){
a = 1000;
console.log('a1-',a);
}
console.log('a2-',a);
fn();
console.log('a3-',a);
// a2- 100 // 在当前作用域下查找变量a => 100
// a1- 1000 // 函数执行时,全局变量a已经被重新赋值
// a3- 1000 // 全局变量a => 1000
2.3 局部作用域
局部作用域一般只在固定的代码片段内可访问到,最常见的就是以函数为单位的:
function fn(){
var name="余光";
function childFn(){
console.log(name);
}
childFn(); // 余光
}
console.log(name); // name is not defined
三、作用域链
3.1 当查找变量的时候都发生了什么?
- 会先从当前上下文的变量对象中查找;
- 如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找;
- 一直找到全局上下文的变量对象,也就是全局对象;
- 作用域链的顶端就是全局对象;
这样由多个执行上下文的变量对象构成的链表就叫做作用域链,从某种意义上很类似原型和原型链。
3.2 作用域链和原型继承查找时的区别:
- 查找一个普通对象的属性,但是在当前对象和其原型中都找不到时,会返回
undefined
- 查找的属性在作用域链中不存在的话就会抛出
ReferenceError
。
3.3 作用域嵌套
既然每一个函数就可以形成一个作用域(词法作用域
|| 块级作用域
),那么当然也会存在多个作用域嵌套的情况,他们遵循这样的查询规则:
- 内部作用域有权访问外部作用域;
- 外部作用域无法访问内部作用域;(真是是这样吗?)
- 兄弟作用域不可互相访问;
在《你不知道的Js》中,希望读者可以将作用域的嵌套和作用域链想象成这样:
四、思考与总结
4.1 总结
4.2 思考
最后,让我们看一个《JavaScript权威指南》中的两段代码:
var scope = "global scope";
function checkscope1(){
var scope = "local scope";
function f(){
return scope;
}
return f(); // 注意
}
checkscope1();
var scope = "global scope";
function checkscope2(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope2()();
两段代码的结果都是"local scope",书中的回答是:JavaScript 函数的执行用到了作用域链,这个作用域链是在函数定义的时候创建的。嵌套的函数 f() 定义在这个作用域链里,其中的变量 scope 一定是局部变量,不管何时何地执行函数 f(),这种绑定在执行 f() 时依然有效。
但是它们内部经历的事情是一样的吗?
# 参考
- 《你不知道的JavaScript》
- JavaScript深入之词法作用域和动态作用域 (opens new window)
写在最后
JavaScript内功基础部分已经总结到第三篇了,总结这个系列是受到了冴羽 (opens new window)大大的鼓励和启发,本系列大约会有15篇文章,都是我们在面试最高频的,但在工作中常常被忽略的。
JavaScript内功系列:
- this指向详解,思维脑图与代码的结合,让你一篇搞懂this、call、apply。系列(一) (opens new window)
- 从原型到原型链,修炼JavaScript内功这篇文章真的不能错过!系列(二) (opens new window)
- 本文
- 下一篇预发:执行上下文
关于我
- 花名:余光
- 一名工作不久的前端小白
其他沉淀
如果您看到了最后,不妨收藏、点赞、评论一下吧!!! 持续更新,您的三连就是我最大的动力,虚心接受大佬们的批评和指点,共勉!