javascript做用域总结

做用域

当你声明一个函数的时候,你就建立了一个做用域气泡。前期的js只有函数能够建立做用域,现代js引入了块级做用域,每进入一次花括号{}就生成了一个块级做用域。javascript

做用域树

有些人会叫做用域链,可是我以为这应该是个树形结构。树的顶层是全局做用域,在做用域内声明函数会建立函数做用域子节点,遇{}会建立块级做用域子节点,从而构成一颗树html

function a{
    if(true){

    }
}
function b{

}复制代码

如上代码构成以下做用域树
java

做用域树
做用域树

变量声明 var,function,let,const

var、function声明的变量依附最近的函数做用域或全局做用域,let、const声明的变量依附于最近的块级做用域、函数做用域或全局做用域,即全部做用域气泡。c++

{
    var a=1;
    function b(){}
}
for(var c = 1;c<=1;c++){
    var d =1;
    function e(){}
}
if(true){
    var f=1;
    function g(){}
}
function h (){
    var i =1;
}
//  a,b,c,d,e,f,g,h上层无函数声明,依附全局做用域,均可以访问到
console.log(a);
console.log(b);
console.log(c);
console.log(d);
console.log(e);
console.log(f);
console.log(h);
// i 依附h函数做用域,没法访问到
console.log(i);//reference error

{
    let j=1;
    const k=1;
}
for(let l = 1;l<=1;l++){
    let m =1;
}
if(true){
    let n=1;
}
function p (){
    let o =1;
}
// 依附最近的做用域,没法访问到
console.log(j);//reference error 
console.log(k);//reference error
console.log(l);//reference error
console.log(m);//reference error
console.log(n);//reference error
console.log(o);//reference error复制代码

变量值查找

在当前做用域往上查找,找到第一个匹配的标识符时中止返回变量值,若是查到全局做用域都没有,则抛出reference error错误,所以变量值查找有就近原则es6

javascript 代码执行前有编译,产生变量提高

javascript 代码执行前要通过编译环节,该环节会生成部分做用域树(有些是动态生成),以下代码bash

console.log(a);// 输出a的函数定义
function a() {
    console.log(b);// undefined
    var b = 2;
    console.log(b);// 2
}复制代码

通过编译环节后,等价于babel

function a() {
    var b;
    console.log(b);
    b = 2;
    console.log(b);
}
console.log(a);复制代码

只有function和var这种变量声明会变量提高,而let,const不会。变量提高的时候,函数声明会首先被提高,而后才是变量。(个人记忆方法是函数的第一公民,权利最大)闭包

foo(); // 1
function foo() { 
    console.log( 1 );
}
var foo = function() { 
    console.log( 2 );
};
foo();// 2复制代码

通过编译环节后异步

function foo() { 
    console.log( 1 );
}
var foo;// 已经声明过,被忽略
foo(); // 1
foo = function() { console.log( 2 );};
foo(); // 2复制代码

function,var,let,const的区别

  1. function,var声明的变量依附在最近函数做用域或全局做用域,let,const声明的变量依附在最近块级做用域、函数做用域或全局做用域
  2. function,var声明的变量在编译阶段产生变量提高,且函数优先提高。let,const不会产生变量提高
  3. fucntion,var在同一做用域重复声明变量,后者会覆盖前者(前者与后者的关系要看编译环节事后的代码);而let,const 会直接抛出语法错误
  4. const 声明变量的同时须要赋值,不然抛出语法错误,且变量的指向不能变(可是变量指向的内容能够变)
const a;//报错,没有赋值初值
const b = {a:1};
b.a = 2;//没错,变量指向的内容能够变
b = {a:2}//出错,变量的指向不能变复制代码

严格模式'use strict'

这块内容在笔试题上反映吧
详细看这篇博客函数

块级做用域的实现

块级做用域是es6才有的东西,以前的javascript引擎并不支持,因而就有了babel编译神器,将es6的代码编译成低版本javascript引擎能正确执行的代码。那babel是怎么实现块级做用域的呢,答案就是闭包和变量更名。

image
image

image
image

做用域相关笔试题

第一题:

console.log(a());// 2
var a = function b(){
    console.log(1);
}
console.log(a());// 1
function a(){
    console.log(2);
}
console.log(a());// 1
console.log(b());// reference error复制代码

代码编译后,变量提高,函数优先,赋值语句中b为右值,非变量声明,因此代码等价于

function a(){
    console.log(2);
}
var a;
console.log(a());// 2
a = function (){
    console.log(1);
}
console.log(a());// 1

console.log(a());// 1
console.log(b());// reference error复制代码

第二题:

function test() {
    console.log(a);// undefined
    console.log(b);// reference error
    console.log(c);// reference error
    var a = b =1;// 等价于 var a=1;b=1;
    let c = 1;
}
test();
console.log(b);// 1
console.log(a);// reference error复制代码

var 声明的变量会变量提高,并依附函数做用域,而,b变量未声明就做为左值,会变成全局变量,可是不会变量提高;

第三题:

"use strict";
function test() {
    console.log(a);// undefined
    console.log(b);// reference error
    console.log(c);// reference error
    var a = b =1;// 直接抛出语法错误
    let c = 1;
}
test();
console.log(b);// reference error
console.log(a);// reference error复制代码

进入严格模式后,b=1这种语法会直接出错,不会变成全局变量

第四题:
4.1题

for(var i=0;i<5;i++){
  setTimeout(function(){console.log(i)},0); // 5 5 5 5 5 
}复制代码

i 依附函数做用域,执行过程只有一个i,而setTimeout是异步函数,须要等栈中的代码执行完后再执行,此时i已经变为5

4.2题

for(let i=0;i<5;i++){
  setTimeout(function(){console.log(i)},0); // 1 2  3  4
}复制代码

let 依附for的块级做用域,代码等价于

for(let i=0;i<5;i++){
  let j = i;
  setTimeout(function(){console.log(j)},0); // 1 2  3  4
}复制代码

能够看出每次循环都产生一个新的内存单元,异步函数执行时,取到的值为当时保持的快照值。

4.3题

for(var i=0;i<5;i++){
 (function(i){
   setTimeout(function(){console.log(i)},0); // 1 2  3  4
  })(i);
}复制代码

使用IIFE来建立快照值,将for 循环的i 传递给当即执行表达式中的参数i,i是基本数据类型,为赋值传递,会产生一个新的内存单元,每次循环都产生一个快照值,因此异步函数执行的时候,取到的值为当时保持的快照值。

4.4题

for(var i={j:0};i.j<5;i.j++){
 (function(i){
   setTimeout(function(){console.log(i.j)},0); // 5 5 5 5 5
  })(i);
}复制代码

i不是基本数据类型,为引用传递,i始终指向同一内存单元

4.5题

for(let i={j:0};i.j<5;i.j++){
   setTimeout(function(){console.log(i.j)},0); // 5 5 5 5 5
}复制代码

与上面4.4题同理

4.6题
如何改变4.4题的当即执行表达式,输出1,2,3,4

for(var i={j:0};i.j<5;i.j++){
 (function(i){
   setTimeout(function(){console.log(i.j)},0); // 1 2 3 4
  })(JSON.parse(JSON.stringify(i)));
}复制代码

将i序列化和反序列化,产生新的内存单元值,不让i指向同一内存单元

相关文章
相关标签/搜索