详解函数作用域

lihaixing

一、变量提升

1、变量

var 表示是变量 存在提前申明 同时也会和window存在映射机制(只存在于全局变量和window之间)

console.log(a); // undefined  没报错,变量提升了
console.log(window.a, 'a' in window) // undefined true
var a = 5;
console.log(a); // 5
console.log(window.a, 'a' in window) // 5 true

没有var就不是变量,也就不存在变量提升,相当于 window.b=9

console.log(b)  // Uncaught ReferenceError: b is not defined
console.log(window.b, 'b' in window) // undefined false
b = 9;
console.log(b) // 9
console.log(window.b, 'b' in window)  // 9 true

var c = d = 10; 相当于

var c=10;
d=10;

2、函数

function和var变量得区别是:function不仅提前声明,还提前定义(赋值)

另外,function由于是引用类型,定义的时候其实是赋值了一个引用地址,这个引用地址指向某个堆内存,这个堆内存里存着这个函数字符串

var f1 = () => {
    console.log(2)
}
function f1() {
    console.log(1)
}
f1(); // 2  因为function f1提前声明定义了

函数内部变量提升, 私有变量不带var, 就向上级索取

console.log(a, b);  // undefined undefined
var a = 12, b = 12;
function fn() {
    console.log(a, b); // undefined 12  b不带var 向上级索取
    
    // 这里b赋值,其实是window.b=13, 由于全局作用域变量b和         window.b存在映射关系,外部b变量也成了13
    var a = b = 13; 
    console.log(a, b) // 13 13
}
fn();
console.log(a, b); // 12 13

条件语句中的变量提升

  • 条件语句中的var变量仍然会变量提升
  • 条件语句中的函数也会变量提升,但只是声明,不会定义
  • 如果条件成立,条件中第一步就是先给之前预检查时只声明没定义的函数定义
console.log(c); // undefined
console.log(f1); // undefined  变量提升了 但只是声明,不定义
if (1) {
    console.log(f1); // funciton f1(){}  如果条件成立,在块级作用域中,先把变量提升的函数赋值(因为之前只是声明了,没有赋值)
    var c = 2;
    function f1() {
        console.log('f1');
    }
}
console.log(f1); // funciton f1(){}
console.log(c);  // 2

当然要是条件不成立,那if中也不会赋值

console.log(c); // undefined
console.log(f1); // undefined  变量提升了 但只是声明,不定义
if (0) {
    console.log(f1);
    var c = 2;
    function f1() {
        console.log('f1');
    }
}
console.log(f1); // undefined
console.log(c);  // undefined

下一题

f = function () { return true }
g = function () { return false }
~function () {
    if (g() && [] == ![]) {  // 行4 这里会报错 [] == ![] true
        f = function () { return false }
        function g() {
            return true
        }
    }
}();
console.log('f()', f())
console.log('g()', g())

解析:在第四行就报错了,执行不下去了,为什么,因为在自执行函数中,if之前,function g提前声明了,但没有赋值;结果if判断条件g() && [] == ![]去调了,但此时g还是undefined呢

变换一下,if判断条件中g()换为f()

f = function () { return true }
g = function () { return false }
~function () {
    if (f() && [] == ![]) {  // 行4 [] == ![] true
        f = function () { return false }
        function g() {
            return true
        }
    }
}();
console.log('f()', f()) // false
console.log('g()', g()) // false

解析:由于自执行函数中没有f函数和f变量,不存在提前申明,所以if判断中f()调用全局作用域的f,所以为true; 然后执行if语句中代码,全局作用域中的f被重新赋值(return false),而g函数只是在自执行函数中生效,不会影响全局作用域中的g

二、重命名

1、var和function名称重复会相互覆盖

ff() // ff
var ff =12;
ff()  // 报错 因为此时ff是12,不是函数
function ff(){
    console.log('ff')
}
ff() // 报错 因为此时ff是12,不是函数

2、js很懒,如果已经声明且赋值,再声明赋值,只会重新赋值

fn();  // 4
function fn() { console.log(1) }
fn(); // 4
function fn() { console.log(2) }
fn(); // 4
var fn = 2;
fn(); // not function error
function fn() { console.log(3) }
fn(); // not function error
function fn() { console.log(4) }
fn(); // not function error

三、let const

1、let const 不存在变量提升 且和window切断映射

console.log(l); // error
let l = 5;
console.log(window.l) // undefined
console.log('l' in window) // false

2、浏览器执行之前会检测使用let得变量, 会提前报错

let a=12;
a=12;
console.log(a) // 报错 从执行上这一行还没出错,但是提前检测了
let a=13; // 报错,已经声明了
var a=13; // 报错

其实第一行就报错了

var b=12; // 报错
let b=13; 

3、暂时性死区

let a = 10, b = 10;
let fn = function () {
    console.log(a) // 报错 因为函数作用域中定义了let a
    console.log(b) // 10
    let a = b = 20
    console.log(a) // 20
    console.log(b) // 20
}
fn()
console.log(a, b) // 10 20
var a = 12;
if (true) {  // let会形成{}块级作用域
    console.log(a);  // 报错
    let a = 13;
    console.log(a) // 13
}
console.log(a)  // 12

四、作用域

1、函数得上级作用域是谁,和执行时的位置没有关系,和该函数定义的位置有关系

var a = 12;
function fn2() {
    console.log(a)  // 12
}
function sum() {
    var a = 20;
    fn2();
    console.log(a); //20
}
sum();

2、闭包

var n = 10;
function fn3() {
    var n = 20;
    function fn4() {
        n++;
        console.log(n)
    }
    fn4();
    return fn4;
}
var x = fn3(); // 21
x(); //22
x(); //23
console.log(n); //10

`
堆内存:存储引用数据类型值(对象:键值对,函数:代码字符串)

  • 1) 堆内存释放 让引用堆内存地址的变量赋值为null
  • 栈内存:提供js代码执行环境,存储基本类型值
  • 1) 栈内存释放:当函数执行完成, 所形成的私有作用域自动释放,
  • 但如果栈内存某内容被栈内存以外的变量引用了,就不能释放
  • 2) 全局栈内存只有页面关闭才能释放

`

五、鄙视题

私有作用域首先是形参赋值,然后是变量提升,再然后才是代码执行

var a = 12, b = 13, c = 14;
function fn(a) {
    // 私有作用域 首先形参赋值 a=12
    // 然后变量提升 var b;
    // var function 形参 都是私有变量
    console.log(a, b, c) // 12 undefined 14
    var b = c = a = 20;
    console.log(a, b, c) // 20 20 20
}
fn(a); // 传入实参12
console.log(a, b, c) // 12 13 20

衍生一下

var arr = [12, 23];
function fn1(arr) {
    console.log(arr); // [12, 23]
    // 这样赋值,改的不仅仅是实参,而且把全局中的arr[0]也改了,全局arr成了[100.23]
    arr[0] = 100; 
    arr = [100];
    arr[0] = 0;
    console.log(arr); // [0]
}
fn1(arr);
console.log(arr); // [100,23]
阅读 956

大前端
专注前端开发,分享技术难题,共同进步
31 声望
6 粉丝
0 条评论
你知道吗?

31 声望
6 粉丝
文章目录
宣传栏