JavaScript中的作用域
- 最近已经开始试着做一些轻量级的开发了,也发现了自己的很多基础问题,最近几天再试着做一点点整理
- 首先JavaScript中的作用域是真的让我头疼啊,以前C里面的作用域说白了就是一个块级作用域的问题。。。ES5和ES6不是很统一,就让我很头大。。
- 全局作用域
当你在文档中(document)编写 JavaScript 时,你就已经在全局作用域中了。JavaScript 文档中(document)只有一个全局作用域。定义在函数之外的变量会被保存在全局作用域中。全局作用域里的变量能够在其他作用域中被访问和修改。 - 局部作用域
定义在函数中的变量就在局部作用域中。并且函数在每次调用时都有一个不同的作用域。这意味着同名变量可以用在不同的函数中。因为这些变量绑定在不同的函数中,拥有不同作用域,彼此之间不能访问。 - 块语句
块级声明包括if和switch,以及for和while循环,和函数不同,它们不会创建新的作用域。在块级声明中定义的变量从属于该块所在的作用域。
ECMAScript 6 引入了let和const关键字。这些关键字可以代替var。
和var关键字不同,let和const关键字支持在块级声明中创建使用局部作用域。 - 上下文
上下文就涉及到我最喜欢又最讨厌的this了
在全局作用域中,上下文总是window对象
如果作用域定义在一个对象的方法中,上下文就是这个方法所在的那个对象
lass User {
logName() {
console.log(this);
}
}
(new User).logName(); // logs User {}
- 那么一个函数的作用域到底是什么呢?
在一个函数被调用的时候,函数的作用域才会存在。此时,在函数还没有开始执行的时候,开始创建函数的作用域:
函数作用域的创建步骤:
1.函数形参的声明。
2.函数变量的声明
3.普通变量的声明。
4.函数内部的this指针赋值
执行环境的创建阶段有三件事:
1. 创建变量对象
2. 创建作用域链
3. 设置上下文(this)的值
- 【讨论闭包】
什么叫闭包?
函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机学文献中称为闭包
所以,所有JavaScript函数都是闭包,他们都是对象,都关联到作用域链
我们要理解几个关键概念- 函数的执行依赖于变量作用域,这个作用域是在函数定义是决定的,而不是函数调用是决定的。
- 闭包所引起的问题
A. 变量污染:
如:
var funB,
funC;
(function() {
var a = 1;
funB = function () {
a = a + 1;
console.log(a);
}
funC = function () {
a = a + 1;
console.log(a);
}
}());
funB(); //2
funC(); //3.
因为funB和funC作用域里面都没有定义a,他们就会在定义funB和分funC的环境中一级一级向上找a的定义,找到的a定义应该是一样的
再举一个最经典的关于闭包的例题:
var array = [
];
for (var i = 0; i < 10; i++) {
var fun = function () {
console.log(i);
}
array.push(fun);
}
var index = array.length;
while (index > 0) {
array[--index]();
} //输出结果 全是10;
在讨论这个结果之前我们可以先看两个对比
//代码1:
var scope='glocal scope';//全局变量
function checkscope(){
var scope='local scope';
function f(){
return scope;
}
return f();
}
alert(checkscope());//local scope
//代码2:
var scope='glocal scope';//全局变量
function checkscope(){
var scope='local scope';
function f(){
return scope;
}
return f;
}
alert(checkscope()())//local scope
结果是一样的。为什么呢?因为前面说过:**函数的执行依赖于变量作用域,这个作用域是在函数定义是决定的,而不是函数调用是决定的。**第一个好理解,第二个我们可以看到返回的不是一个调用返回值,而是一个函数对象,在全局环境下调用。但是实际上,作用域还是在函数的定义上,所以最后的alert出来还是local scope
回到我们开始讨论的输出结果,为什么全是10呢?
我试着在全局环境下再在控制台输出(话说到这里,为什么我的谷歌控制台看不到输出啊。。。麻蛋,又逼着我去用火狐)i的值,i发现i在后面调用的时候,就已经是10了,i是用var定义在全局作用域下的。
这种类似问题产生的根源就在于,没有注意到外部作用域链上的所有变量均是静态的。
B.内存泄露
就是内存溢出。为什么会容易溢出呢?
var fun = undefined;
function A() {
var a = 1;
fun = function () {
}
}
这里面的变量a将会在函数fun存在的域中一直存在。如果牵扯到DOM操作,遇到一个贼你妈大的DOM ,那就凉凉了。
所以闭包会引起这么多问题。如何解决呢?
大概就是用C的函数参数的思路:
var funB,funC;
(function () {
var a = 1;
(function () {
funB = function () {
a = a + 1;
console.log(a);
}
}(a));
(function (a) {
funC = function () {
a = a + 1;
console.log(a);
}
}(a));
}());
funB()||funC(); //输出结果全是2 另外也没有改变作用域链上a的值。
再举一个应用层的例子:
onPullDownRefresh: function () {
wx.showNavigationBarLoading();
var that = this
const db = wx.cloud.database()
db.collection('user').get({
success: function (res) {
console.log(res.data.reverse());
//将获取到的json数据,存在名字叫list的这个数组中
that.setData({
liuyanlist: res.data
//res代表success函数的事件对,data是固定的,liuyanlist是数组
})
console.log(that.data.liuyanlist)
}
})
// 隐藏导航栏加载框
wx.hideNavigationBarLoading();
// 停止下拉动作
wx.stopPullDownRefresh();
},
这告诉了我们一点:作用域链是沿着执行环境走的
更加深入的理解可以参照我在其他博客里面找到 一段话:
为了解决掉我们从上面学习中会出现的各种困惑,“执行环境(context)”这个词中的“环境(context)”指的是作用域而并非上下文。这是一个怪异的命名约定,但由于 JavaScript 的文档如此,我们只好也这样约定。
JavaScript 是一种单线程语言,所以它同一时间只能执行单个任务。其他任务排列在执行环境中。当 JavaScript 解析器开始执行你的代码,环境(作用域)默认设为全局。全局环境添加到你的执行环境中,事实上这是执行环境里的第一个环境。
之后,每个函数调用都会添加它的环境到执行环境中。无论是函数内部还是其他地方调用函数,都会是相同的过程。
每个函数都会创建它自己的执行环境。
当浏览器执行完环境中的代码,这个环境会从执行环境中弹出,执行环境中当前环境的状态会转移到父级环境。浏览器总是先执行在执行栈顶的执行环境(事实上就是你代码最里层的作用域)。
全局环境只能有一个,函数环境可以有任意多个。
执行环境有两个阶段:创建和执行。