JS基础知识03——作用域和闭包

1.作用域

作用域其实就代表了变量合法的使用范围。
作用域分为全局作用域、函数作用域和块级作用域。
在这里插入图片描述
Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。

var n=999;
function f1(){
  alert(n);
}
f1(); // 999

但是,在函数外部自然无法读取函数内的局部变量。

function f1(){
  var n=999;
}

alert(n); // error

这里有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!

function f1(){
  n=999;
}
f1();
alert(n); // 999

2.作用域链

一个变量在当前作用域没有被定义,但被使用了,会向上级作用域,一层层寻找,直至找到为止。
这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。

function f1(){
   var n=999;
   function f2(){
     alert(n); // n在这个作用域内就是自由变量,999
  }
}
LHS和RHS

LHS、RHS,是执行代码的时候,查询变量的两种方式。这个“左”和“右”,是相对于赋值操作来说的。当变量出现在赋值操作的左侧时,执行的就是 LHS 操作,右侧则执行 RHS 操作.
LHS 意味着 变量赋值或写入内存,它强调的是一个写入的动作,所以 LHS 查询查的是这个变量的“家”(对应的内存空间)在哪。

name = 'xiuyan';
var myName = name

RHS 意味着 变量查找或从内存中读取,它强调的是读这个动作,查询的是变量的内容。

console.log(name)

3.闭包

引用了自由变量的函数,就叫闭包。
自由变量:在函数中被使用,但它既不是函数参数、也不是函数的局部变量,而是一个不属于当前作用域的变量,此时它相对于当前作用域来说,就是一个自由变量。
所有自由变量的查找,是在函数定义的地方,向上级作用域查找,而不是在执行的地方!!!!!! 自由变量的查找遵循词法作用域

闭包的作用:
  1. 在外部访问到函数内部的变量
    js作用域中,函数内部可以调用函数外部的变量,反之,却不可以,可以使用闭包解决,在外部访问到函数内部的变量。

    function outer() {
        const a = 100;
         return function inner() {
              console.log(a)
         }
    }
    let fn = outer();
    const a = 200;
    fn();     //100
    
  2. 让函数内部的变量值始终保持在内存中。
    一般情况下, 函数执行完后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象),但是闭包情况下,比如下面这个代码,函数f1执行完后,变量n不会被摧毁,因为f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。所以闭包,会占用更多的占用内存。

    function f1(){
      var n=999;
      return function f2(){
      	n++
        alert(n);
      }
    }
    var result=f1();
    result(); // 1000
    result(); // 1001
    //函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
    

3. 闭包的几种表现形式

根据闭包的定义,我们知道,无论通过何种手段,只要将内部函数传递到所在的词法作用域以外,它都会持有对原始作用域的引用,无论在何处执行这个函数都会使用闭包。接下来,本文将详细介绍闭包的7种形式。
闭包的两种重要应用场景为:函数作为返回值被返回,函数作为参数被传入

函数作为返回值返回
function outer() {
	    const a = 100;
	     return function inner() {
	          console.log(a)
	     }
	}
let fn = outer();
const a = 200;
fn();     //100
将内部函数赋值给一个外部变量

这是将函数作为返回值的一种变形。
(将F作用域里的函数N赋值给全局作用域的inner,所以F执行之后,inner保持了对F作用域里的引用)

var inner;
function F(){
    var b = 'local';
    inner = functionN(){
        return b;
    };
};
F();
console.log(inner());//local
函数作为参数

闭包可以通过函数参数传递函数的形式来实现

const b = 100;
function fn(){
    console.log(b);
}
function print(fn) {
   const b = 200;
   fn();
}
print(fn); //100
IIFE

由前面的示例代码可知,外部函数都是在声明后立即被调用,因此可以使用IIFE来替代。但是,要注意的是,这里的Inner()只能使用函数声明语句的形式,而不能使用函数表达式。

function Inner(fn){
    console.log(fn());
}

(function(){
    var b = 'local';
     Inner(function(){
        return b;
    })
})();

闭包的应用

1. 用自由变量模拟私有变量的实现

希望它仅在对象内部生效,无法从外部触及,这样的变量,就是私有变量。
把私有变量用函数作用域来保护起来,形成一个闭包!

const User = (function() {
    // 定义私有变量_password
    let _password

    class User {
        constructor (username, password) {
            // 初始化私有变量_password
            _password = password
            this.username = username
        }

       login() {
           // 这里我们增加一行 console,为了验证 login 里仍可以顺利拿到密码
           console.log(this.username, _password)
           // 使用 fetch 进行登录请求,同上,此处省略
       }
    }

    return User
})()

let user = new User('xiuyan', 'xiuyan123')

console.log(user.username)   // xiuyan
console.log(user.password)   // undefined
console.log(user._password)  // undefined
user.login()   // xiuyan xiuyan123

在这段代码中,我们把 _password 放在了 login 方法的外层函数作用域里,并通过立即执行 User 这个函数,创造出了一个闭包的作用域环境。

2. 柯里化与偏函数

柯里化是把接受 n 个参数的 1 个函数改造为只接受 1个参数的 n 个互相嵌套的函数的过程。也就是 fn (a, b, c)fn(a,b,c) 会变成 fn (a)(b)©。

function generateName(prefix) {  
    return function(type) {
        return function (itemName) {
            return prefix + type + itemName
        }    
    }
}

// 生成大卖网商品名专属函数
var salesName = generateName('大卖网')

// “记住”prefix,生成大卖网母婴商品名专属函数
var salesBabyName = salesName('母婴')

// "记住“prefix和type,生成洗菜网生鲜商品名专属函数
var vegFreshName = generateName('洗菜网')('生鲜')

// 输出 '大卖网母婴奶瓶'
salesBabyName('奶瓶')
// 输出 '洗菜网生鲜菠菜'
vegFreshName('菠菜')

// 啥也不记,直接生成一个商品名
var itemFullName = generateName('洗菜网')('生鲜')('菠菜')

偏函数应用是不强调 “单参数” 这个概念的。它的目标仅仅是把函数的入参拆解为两部分。

function generateName(prefix) {
    return function(type, itemName) {
        return prefix + type + itemName
    }
}

// 把3个参数分两部分传入
var itemFullName = generateName('大卖网')('母婴', '奶瓶')
3.节流防抖
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值