富民网站建设招工 最新招聘信息
文章目录
- 作用域、作用域链
- 作用域
- 作用域链
- 循环中的作用域
- 自由变量、闭包
- 自由变量
- 闭包的定义、表现、应用
- 如何确定在闭包中获取正确的变量
- 总结
作用域、作用域链
作用域
编程语言中存储、访问、修改变量当中的值是一项基本能力、存储变量、访问变量必须按照一定的规则,这套规则就是作用域。JavaScript中的作用域可分为三种:全局作用域、函数作用域、块作用域
- 全局作用域
在任何函数之外的顶层作用域,
全局可使用
,如windows对象,document对象, 全局变量在全局作用域、函数作用域和块作用域里都可以获取到。
// 全局作用域var temp = 'A'; // 函数作用域function showTemp() {console.log(temp);}console.log(temp) // AshowTemp(); // A// 块作用域{temp = 'B'}console.log(temp) // B
- 函数作用域
函数中定义的作用域,只能在
当前函数中使用
。
// 函数作用域function showTemp() {var temp = 'A'console.log(temp);}showTemp(); // Aconsole.log(temp) // 报错: temp not defined
- 块作用域
- ES6 新增的两个用于声明变量的新关键词
let
和const
。这两个关键字定义的变量如果处于大括号 { } 中,大括号中的变量就形成了一个块作用域。- 在
if/while/for
等的大括号{ }里也形成了一个块作用域。
{let temp = 'A'}{console.log(temp) // 报错: temp not defined} console.log(temp) // 报错: temp not defined
作用域链
实际工程中,通常会使用多种作用域。当在当前作用域中无法找到目标变量时,就会向上级作用域寻找,这一层层向上的过程称为
作用域链
。
const A = 1;function printSum(A) {const B = 2console.log(A + B)} printSum(A) // 3
上面是一个简单的示例,printSum函数中找到了需要的变量 B ,但是找不到变量 A ,于是沿着 作用域链 找到了 全局作用域 的目标变量A。
循环中的作用域
for (var i = 0; i < 5; i++) {setTimeout(function () {console.log(i);}, 1000);}
上面是一道经典的面试题目,最终会输出五个5,根据作用域的理论,在function中找不到变量i,当向上层寻找时,for循环里的i早已经执行到 i=5, 函数在延迟执行后取到的i只会是5。要实现预计的0-4打印效果,可以在 setTimeout 外面再套一层函数,或者在循环中使用let:
var print = function (i) {setTimeout(function () {console.log(i);}, 1000);};for (var i = 0; i < 5; i++) {print(i); // 会去print函数的作用域去寻找变量 i}// 0 1 2 3 4for (let i = 0; i < 5; i++) {setTimeout(function () {console.log(i);}, 1000);}// 0 1 2 3 4
自由变量、闭包
自由变量
- 定义:某变量 a 在作用域 A 中被使用,却没有在该作用域中被定义,需要沿
作用域链
寻找,则对于作用域A来说,a是一个自由变量
。- 自由变量确定:沿
作用域链
向上级作用域一层层寻找,直到找到;若在全局作用域都没找到,会报错:xxx is not defined
。
闭包的定义、表现、应用
如果一个函数引用了
自由变量
,即该函数使用了某变量,但它既 不是函数参数
、也不是函数内部定义的变量
,则该函数就叫闭包
。
闭包通常有两种表现:函数作为返回值、 函数作为参数被传递。
- 函数作为返回值
function closure() {const a = 100// 返回值函数return function () { console.log(a) // 100}} // 把返回值函数赋给fnconst fn1 = closure() fn1()
- 函数作为参数被传递
function closure() {const a = 100// 返回值函数return function () { console.log(a) // 100}} // 把返回值函数赋给fnconst fn1 = closure() fn1()
通过上述描述其实可以看到:闭包是作用域应用的一种特殊表现形式。那么,闭包到底有什么作用呢?一句话:闭包可以使变量仅在对象内部生效,无法从外部触及,只提供API,从而保护数据
。
举例一: 闭包隐藏数据,不能直接修改数据
function cache() {const data = {} // data是在函数cache作用域中被定义的,全局中未定义return {set: function (key, val) {data[key] = val},get: function (key) {return data[key]}}}const data = cache()data.set('name', 'jackeroo')const data_name = data.get('name')console.log(data_name) // jackerooconsole.log(data.name) // undefiend 无法直接获取name属性
举例二: 创建一个User对象,能够调取它的login方法获取用户名和密码,也能够直接访问该用户的用户名,但是不能取到该用户的密码
const User = function () {let _password;return class User {constructor(name, password) {this.userName = name;_password = password;}login() {console.log(`使用账号:${this.userName}, 密码:${_password}进行登录`)}}}()const theUser = new User('jackeroo', 123)theUser.login() // 使用账号:jackeroo, 密码:123进行登录console.log(theUser.userName) // jackerooconsole.log(theUser.password, theUser._password) // undefined undefined
如何确定在闭包中获取正确的变量
⭐在函数定义的地方向上级作用域查找,注意是函数定义的地方而不在函数执行处
// 示例1
function create(){const a = 100return function (){ // 函数的定义处console.log(a) }
}
const a = 200
const fn1 = create() // 函数执行处
fn1()//100// 示例2
function print(fn2){ const b = 200fn2() //函数执行处
}
const b = 100
function fn2(){ //函数定义处console.log(b)
}
print(fn2) // 100// 示例3
const c = 1;
function test(){a = 2;return function(){ // 函数定义处console.log(a);}var a = 3; // 变量提升 => var a = 2
}
test()(); // 2
通过以上三个例子再次强调:闭包/所有自由变量的查找是在函数定义的地方向上级作用域查找,而不是在函数执行的地方!!!
总结
作用域、作用域链
- 作用域
- 作用域链
自由变量、闭包
- 自由变量
- 闭包的定义、表现、应用
如何确定在闭包中获取正确的变量
闭包中的自由变量的查找是在函数定义的地方向上级作用域查找