1. window全局作用域 全局变量
var abc = 123;
bcd = 234;
// var 定义的这个abc肯定是全局变量。 且是挂在window下面的。 window.abc可以访问到
// bcd不直接定义,也是挂在在window下,可以访问到。 window.bcd window是个全局对象。
// 区别,bcd不能说是变量,只能说是window的属性; 变量不能删除,属性可以删除
delete abc; // false 也可以是 delete window.abc 变量,不能被删除,还是可以访问。
delete bcd; // true 也可以是 delete window.bcd 删除了,访问不到
var abc = 123;
// 这个全局变量,可以在整个文件访问到。 例如:index.html 里引入 1.js 和 2.js。 其中1.js里面定义了var abc。 那么2.js也可以访问到
// 函数内部没有var定义的变量,不具备函数作用域,是全局作用域下
function test1(){
ab = 45; // 函数执行之后,这个变量可以在全局都访问到
delete ab; // true 这个和上面bcd一样,相当于window的属性。不是变量
ab = 35;
}
test1();
2. 函数作用域 局部作用域
function test2() {
var a = 3;
if(a === 3){
var b = 4; // var只有函数作用域与全局作用域。 if后面的{}是块状作用域,var没法封锁块级作用域
let c = 5; // let可以在当前块级作用域下封锁,块级作用域以外访问不到c
console.log('aaa');
}else{
console.log('bbb')
}
console.log(b); // 4
console.log(c); // 报错 拿不到c
return a + 4;
}
console.log(test2()); // 7
console.log(a); // 报错 不能拿到a
3. 动态作用域
a = 3;
function test() {
var a = 5;
console.log(this.a);
console.log(a); // 5
}
test(); // 3 5 此时test里面的this指向的是当前函数所处的环境。此时就是全局window
test.bind( {a: 100} )(); // 100 5 bind改变了test里面this的指向。指向了一个新的对象。 这个对象里面a=100
4. 作用域链的理解
对于一个变量,引擎从当前作用域开始查找变量,如果找不到,就会向上一级继续查找。当抵达最外层全局作用域时,无论找到还是没有找到,查找过程中都会停止。
例子:
for(var i = 0; i < 3; i++){
setTimeout(function () {
console.log(i);
}, 1000)
}
// 最后结果,会一次输出3个i。 i当前的作用域是全局。 setTimeout里面,i会向上去找,向上没有函数作用域,直接就找到了全局。 此时,i已经执行完了循环,i=3。
for(let i = 0; i < 3; i++){
setTimeout(function () {
console.log(i);
}, 1000)
}
// 1s之后,会按顺序一次性输出0 1 2。 因为let把当前的i限制在for这个块级作用域下。 setTimeout访问时只会到当前所处的for循环体里去找i
再看一个例子
var liList = document.querySelectorAll('li') // 共5个li
for(var i=0; i<liList.length; i++){
liList[i].onclick = function(){
console.log(i)
}
}
// var声明i 每个li点击后 都是弹出5。
var liList = document.querySelectorAll('li') // 共5个li
for(var i=0; i<liList.length; i++){
liList[i].onclick = function(){
console.log(i)
}
}
// let声明i 每个i可以顺利的打印出 0 1 2 3 4
解释:
- i的声明被提升。
- 当运行for循环时为i赋值,并为每个li绑定事件(注意:运行for循环时只是绑定了事件但是并没有执行事件)。
- 当触发事件时(注意:此时for循环执行完了),现在需要控制台打印i的值,于是i便沿着作用域链寻找它的值。
- 当用var声明时,i会在全局作用域中找到它的值,为5.
- 当用let声明时,i会在for的第一行找到它的值,每次的值不一样,分别为0、1、2、3、4.
同理,再看一个问题:
Question: 生成十个按钮,每个点击的时候弹出1 - 10
// 利用自运行函数,可以成功。
var i = 0;
for (i = 1; i <= 10; i++) {
// 一个自运行的函数 (function (i))(i) 相当于(function (i))(1) (function (i))(2) (function (i))(3) ...
// 这个函数里面的i是独立的作用域,是最后(i)传进来的
(function (i) {
var btn = document.createElement('button');
btn.innerText = i;
btn.onclick = function () {
alert(i);
};
document.body.appendChild(btn);
})(i)
}
// 去掉自运行的函数,结果会生成1-10个按钮,但是弹出来的永远是11
var i = 0;
for (i = 1; i <= 10; i++) {
var btn = document.createElement('button');
// 这个i会遵循1-10的循环,所以生成按钮内容都是1-10
btn.innerText = i;
// 这里的i并不参与循环,相当于给每个按钮绑定一个事件。此时alert里面的值没确定。每次点击按钮的时候,alert(i)就会向上去找i,此时i已经结束循环了,变成11
btn.onclick = function () {
alert(i);
};
document.body.appendChild(btn);
}
// 用let就和其他java等语言一样,这个i只作用于当前for循环的作用域内,不需要自运行函数
for (let i = 1; i <= 10; i++) {
var btn = document.createElement('button');
btn.innerText = i;
btn.onclick = function () {
alert(i);
};
document.body.appendChild(btn);
}