作用域
-
在 JavaScript 中, 对象和函数同样也是变量。
-
在 JavaScript 中, 作用域为可访问变量,对象,函数的集合。
-
JavaScript 函数作用域: 作用域在函数内修改
-
es6中新增了块级作用域(大括号,比如:if{},for(){},while(){}…);
局部作用域
- 变量在函数内声明,变量为局部作用域。
- 局部变量:只能在函数内部访问。
- 只有函数生成私有作用域每一个函数就是一个私有作用域
// 此处不能调用 carName 变量
function myFunction() {
var carName = "Volvo";
// 函数内可调用 carName 变量
}
因为局部变量只作用于函数内,所以不同的函数可以使用相同名称的变量。
局部变量在函数开始执行时创建,函数执行完后局部变量会自动销毁
全局作用域
- 变量在函数外定义,即为全局变量。
- 全局变量有 全局作用域: 网页中所有脚本和函数均可使用
- 整个script标签或者是一个单独的js文件
- 一个 html 页面就是一个全局作用域,打开页面的时候, 作用域就生成了, 直到关闭页面为止
- 如果变量在函数内没有声明(没有使用 var 关键字),该变量为全局变量。
var carName = " Volvo";
// 此处可调用 carName 变量
function myFunction() {
// 函数内可调用 carName 变量
}
以下实例中 carName 在函数内,但是为全局变量。
function myFunction() {
carName = "Volvo";
// 此处可调用 carName 变量
}
myFunction()
// 此处可调用 carName 变量 前提是myFunction函数执行过
console.log(carName) // Volvo
作用域链
一般情况下,变量取值到 创建 这个变量 的函数的作用域中取值。
但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链
-
当代码在一个环境中执行时,会创建变量对象的一个作用域链(作用域形成的链条)
-
作用域链的前端,始终都是当前执行的代码所在环境的变量对象
-
作用域链中的下一个对象来自于外部环境,而在下一个变量对象则来自下一个外部环境,一直到全局执行环境
-
全局执行环境的变量对象始终都是作用域链上的最后一个对象
-
内部环境可以通过作用域链访问所有外部环境,但外部环境不能访问内部环境的任何变量和函数。
var n = 10;
function outer(){
function inner(){
function center(){
console.log(n);
}
center();
}
inner();
var n = 15;
}
outer(); //=> undefined
- 如函数的执行,形成一个私有作用域,形参和当前私有作用域中声明的变量都是私有变量,保存在内部的一个变量对象中,其下一个外部环境可能是函数,也就包含了函数的内部变量对象,直到全局作用域。
- 当在内部函数中,需要访问一个变量的时候,首先会访问函数本身的变量对象,是否有这个变量,如果没有,那么会继续沿作用域链往上查找,直到全局作用域。如果在某个变量对象中找到则使用该变量对象中的变量值。
- 由于变量的查找是沿着作用域链来实现的,所以也称作用域链为变量查找的机制。
- 这个机制也说明了访问局部变量要比访问全局变量更快,因为中间的查找过程更短。但是 JavaScript 引擎在优化标识符查询方面做得很好,因此这个差别可以忽略不计。
ES6中的变量和作用域
通过let和const决定块作用域
let和const创建的变量只在块作用域中有效。它们只存于包含它们的块中。下面演示的代码,通过let在if语句块中声明一个tmp变量。这个变量仅在if语句中有效。
function func() {
if (true) {
let tmp = 123;
console.log(tmp); // => 123
}
}
func() // 123
console.log(tmp); // => ReferenceError: tmp is not defined
相比之下,var声明的变量作用域的范围是函数范围内的:
function func() {
console.log(tmp) // undefined 变量声明提升 还没赋值
if (true) {
var tmp = 123
console.log(tmp) // 123
}
console.log(tmp) // 123
}
func()
console.log(tmp) // Uncaught ReferenceError: tmp is not defined
块作用域意味着你可在有函数内有变量的阴影。
function func() {
let foo = 5;
console.log(foo) // 5
if(true) {
let foo = 10;
console.log(foo) // 10
}
console.log(foo) // 5
}
func()
const创建不可变的变量
由let创建的变量是可变的:
let foo = 'abc'
foo = 'def'
console.log(foo) // def
由const创建的是变量是一个常量,这个变量是不可变的:
const foo = 'abc'
foo = 'def' // Uncaught TypeError: Assignment to constant variable.
如果一个常量指的是一个对象,那么const并不影响常量本身的值是否是可变的,因为它总是指向那个对象,但是对象本身仍然是可以被改变的。
const obj = {}
obj.prop = 123
console.log(obj.prop) // 123
console.log(obj) // {prop: 123}
obj = {} // Uncaught TypeError: Assignment to constant variable.
如果你想让obj真正成为一个常量,你必须冻结它的值:
const obj = Object.freeze({});
obj.prop = 123;
console.log(obj) // {}
也就是说,如果const定义的常量指向的是一个对象。这个时候,它实际上指向的是当前对象的地址。这个地址是在栈里面的,而这个真实的对象是在堆栈里面的。所以,我们使用const定义这个对象后,是可以改变对象的内容的。但是这个地址是不可以改变的。意思也就是不可以给这个对象重新赋值,比如const obj= {}, obj = {},即使是这样,obj好像什么也没有改变,但还是错误的。
然而在普通模式下,并没有报错,而obj.name = 'abc’这是完全可以的。这跟JavaScript存储引用对象的值的方式有密切的关系。
const obj = Object.freeze({})
const newObj = {}
obj.name = 'w3cplus'
newObj.name = 'damo';
console.log(obj) // {}
console.log(newObj) // {name: "damo"}
使用Babel把上面ES6的代码编译成ES5代码:
'use strict';
var obj = Ob
自由变量
- 自由变量的概念:一个变量在当前作用域没有被定义,但被使用了
- 自由变量会向上级作用域一层一层一次寻找,直到找到为止。如果全局作用域都没找到,则报错 xx is not defined
- 相当于Java中的全局变量,它在外层作用域中声明,但在内层作用域中使用
var a = 1;
function fn(){
console.log(a);//a为自由变量
}
当自由变量所属的函数被定义时,自由变量的值就已经确定,是该函数定义处的父作用域中同名变量的值。
在上述示例中,函数内部的a是自由变量,它的值是fn的父作用域中a的值,也就是1。
var aa = 22;
function a(){
alert(aa);
}
function b(fn){
var aa = 11;
fn();
}
b(a); //22
var a = 100
function f1() {
var b = 200
function f2() {
var c = 300;
console.log(a) // a是自由变量
console.log(b) // b是自由变量
console.log(c)
}
f2()
}
f1()
变量提升
JavaScript 中,函数及变量的声明都将被提升到函数的最顶部。
JavaScript 中,变量可以在使用后声明,也就是变量可以先使用再声明。
类不存在变量提升
以下两个实例将获得相同的结果:
例子1
x = 5; // 变量 x 设置为 5
elem = document.getElementById("demo"); // 查找元素
elem.innerHTML = x; // 在元素中显示 x
var x; // 声明 x
例子2
var x; // 声明 x
x = 5; // 变量 x 设置为 5
console.log(x) // 5
要理解以上实例就需要理解 “hoisting(变量提升)”。
变量提升:函数声明和变量声明总是会被解释器悄悄地被"提升"到方法体的最顶部。
类不存在变量提升
new Foo() // Uncaught ReferenceError: Foo is not defined
class Foo {}