【重读你不知道的js Plan】 作用域相关知识点复查【1】

在这里插入图片描述

1. 作用域的概念

1.1 作用域是什么?

在程序当中我们会使用到遍历,而对于这些变量,我们如何存储,以及之后当我们需要改变这些变量,又该如何找到它们呢? 因此在程序中有一种规则去存储变量,方便之后我们去查询,而这套规则就是我们说的作用域。

在传统的编译语言流程中,程序的代码在执行前会经历“编译”(分成三部分):

  1. 分词/词法解析
  • 将字符组成的字符串分解成有意义的代码块(词法单元 token)
  • var a = 2; => var, a, =, 2 , ;
  • 空格的话要具体看空格在这门语言中是否有意义
  1. 解析/语法解析
  • 生成抽象语法树 AST
  1. 代码生成
  • 将语法树AST转换成可执行代码

1.2 理解作用域

  • 引擎:从头到尾复杂js的编译和执行过程
  • 编译器: 负责之前说到的语法分析,代码生成功能
  • 作用域: 收集变量,维护查询,确定当前执行代码对这些变量的访问权限,是一套规则

举个例子:

var a = 2;

这段简单的代码,会由引擎和编译器一起执行。之前说到,在执行一段代码之前,会进行编译,分解成词法单元,然后生成AST树,再执行。这个编译的过程可能比我们想象的复杂一点,说回上面的例子:

编译器会进行入下处理:

  1. var a: 首先编译器会询问作用域(进行变量查询)是否有一个该名称的变量存在同一个作用域中,如果存在,继续编译;否则要求作用域在当前作用域的集合中声明一个新的变量,命名为a
  2. 编译器生成引擎需要执行的代码,a = 2,赋值。这时候作用域又登场了,引擎会询问作用域(进行变量查询)是否有一个该名称的变量存在同一个作用域中,如果存在,使用这个变量;否则继续查找(往作用域链);
  3. 如果找到了该变量,就把2赋给它,否则就抛出错误异常。
    在这里插入图片描述

1.3 LHS RHS

就是在引擎执行代码之前,会通过作用域链查询a是否声明过,但是引擎查找分为两种:

var a = 2; // LHS 变量在赋值的左边 为=a找到一个目标变量
console.log(a); // RHS  (只需找到这个值输出)

1.4 小例子

function foo(a) {
    console.log(a);
}
foo(2); 

以你的角度去分析

function foo(a) {
    console.log(a + b); // 没有b
}
var b = 7;// 往全局找
foo(1)

2. 作用域的分类

2.1 词法作用域/动态作用域(了解)

之前我们说过的,语言在编译阶段的第一个步骤就是词法分析,而词法作用域就是定义在这个阶段的作用域,在你写代码时将变量和块作用域写在哪里来决定的

function foo(a) {
    var b = a * 2;

    function bar(c) {
        console.log(a, b, c);
    }

    bar(b * 3);
}

foo(2)
  • 执行foo()
  • 执行bar()
  • console.log(a,b,c)
  1. bar的作用域里面没有a,b,只有c
  2. 往上找到foo,foo的作用域里面有b,a(a作为foo的参数也是在foo的作用域里面的)

2.2 欺骗词法:(尽量不使用哦,了解一下)

2.2.1 evel

function foo(str, a) {
    eval(str);
    console.log(str) // var b = 3;
    console.log(b) // 3
    console.log(a, b); // 1 3
}

var b = 2;
foo("var b = 3;", 1);

当使用evel函数,里面传递的字符串,会作为一段代码。比如上面的例子就会变成这样:b在函数foo的作用域里面,因此取到的值是3而不是外部的2

function foo(a) {
    var b = 3;
    console.log(a, b); // 1 3
}

var b = 2;
foo(1);

2.2.2 with

  • 作用1
    批量修改对象的属性
var obj = {
    a: 1,
    b: 2,
    c: 3
};

// 不使用with
obj.a = 3;
onj.b = 6;
obj.c = 5


// 使用with
with (obj) {
    a = 3;
    b = 6;
    c = 9;
}

console.log(obj.a, obj.b, obj.c);
  • 作用2 根据传递给它的对象凭空创建一个新的词法作用域
function foo(obj) {
    with (obj) {
        a = 2;
    }
}

var o1 = {
    a : 3
};

var o2 = {
    b : 3
};

foo(o1);
console.log(o1.a);
// 2  o1里面有a,取到a = 2

foo(o2);
console.log(o2.a); // undefined
//  o2里面没b这个属性 找不到 
console.log(a)  // 2
//  但是全局上也创建了一个a = 2,也就是说当o2传就with里面,
//  在当前作用域和全局都没有找到,于是自己创建了一个全局a,再执行了a = 2这个赋值操作

讲一个对象的引用当作作用域来处理,将对象的属性当作作用域的标识符来处理

2.3 函数作用域

函数作用域指的是:属于这个函数的全部变量都可以在整个函数范围内使用及复用(包含嵌套)

2.3.1 适合的写法

function doSometing(a) {
    b = a + doSomethingElse(a * 2);
    console.log(b * 3); // 15
}

function doSomethingElse(a) {
    return a - 1;
}

var b;
doSometing(2); // 15

但是doSometing里面的b和doSomethingElse应该是私有方法,我们不应该把它用到外面去定义,修改成下面这样更好

function doSometing(a) {
    function doSomethingElse(a) {
        return a - 1;
    }
    var b;

    b = a + doSomethingElse(a * 2);

    console.log(b * 3); // 15
}

doSometing(2); // 15

2.3.2 添加包装函数,

可以放在函数内部影响到外部变量
但是:

  1. 需要显示定义foo()函数
  2. 需要执行

var a = 2;
function foo() { // foo作用域 外部不会受到影响
    var a = 3;
    console.log(a);
}

foo();
console.log(a)

立即执行函数解决:

var a = 2;
(function foo() { 
    var a = 3;
    console.log(a);
})();
console.log(a)

以(开头的包装函数,会被当成函数表达式而不是一个标准的函数去执行

小技巧:区分函数表达式和声明式:如果function是声明的第一个词就是函数声明式,否则就是表达式;

小例子:

  1. 匿名函数表达式:回调参数
setTimeout(function () {
    // 代码
},1000)
  1. 立即执行函数: 刚才那个
(function foo() { 
    var a = 3;
    console.log(a);
})();

2.4 块级作用域

for (var i = 0; i < 10; i++) {
    console.log(i) // 打印1-9
}
console.log(i,'1ss') // 打印10 
{
    let j;
    for (j = 0; j < 10; j++) {
        let i = j
        console.log(i) // 打印1-9
    }
}

es6新增了块级作用域,包括声明let const,这里来看看他们和var的区别有哪些吧:

2.5 var let const

  • var声明的变量会挂载在window上,而let和const声明的变量不会:
var a = 1;
let b = 3;
const c = 4;

console.log(a)
console.log(window.a)

console.log(b)
console.log(window.b)

console.log(c)
console.log(window.c)

// 1
// 1
// 3
// undefined
// 4
// undefined

  • var声明变量存在变量提升,let和const不存在变量提升
console.log(a)
console.log(b)
console.log(c)

var a = 0; // undefined var = undefined提升到了前面
let b = 5; // 报错
const c = 6; // 报错
  • let和const声明形成块作用域
  • 同一作用域下let和const不能声明同名变量,而var可以(提升覆盖)
if(1){
    var a = 100;
    let b = 10; // const也一样
}

console.log(a); // 100
console.log(b)  // 报错 b找不到
  • 暂存死区
a = 3
let a;
// ReferenceError: Cannot access 'a' before initialization
  • const 一申明就要赋值
  • const 不能修改值,它是常量
const a = 0;
a = 4
// TypeError: Assignment to constant variable.
  • 如果是复合类型数据,可以修改其属性
const a = {
    aa:'1'
}
a.aa = 'hh'
console.log(a.aa)

写在后面

这个Plan计划,重读你不知道的javascript系列,一起学习吧。目标是每周更新学习记录一部分,加油!

  • 作用域和闭包系列【1】
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值