Js的作用域、作用域链、自由变量、变量提升的知识汇总

作用域

  • 在 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 {}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值