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.闭包
引用了自由变量的函数,就叫闭包。
自由变量:在函数中被使用,但它既不是函数参数、也不是函数的局部变量,而是一个不属于当前作用域的变量,此时它相对于当前作用域来说,就是一个自由变量。
所有自由变量的查找,是在函数定义的地方,向上级作用域查找,而不是在执行的地方!!!!!! 自由变量的查找遵循词法作用域
闭包的作用:
-
在外部访问到函数内部的变量
js作用域中,函数内部可以调用函数外部的变量,反之,却不可以,可以使用闭包解决,在外部访问到函数内部的变量。function outer() { const a = 100; return function inner() { console.log(a) } } let fn = outer(); const a = 200; fn(); //100
-
让函数内部的变量值始终保持在内存中。
一般情况下, 函数执行完后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象),但是闭包情况下,比如下面这个代码,函数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('大卖网')('母婴', '奶瓶')