做用域能够理解为环境上下文,包含了变量、函数声明、参数等。在es6以前,JS使用的是全局做用域和函数做用,无块级做用域。es6
JS有本身特有的做用域链,函数中声明的变量在整个函数中都是有定义的。查找一个变量时,先在变量所在函数体内找,找不到向更外层函数找,一直到全局变量(注:全局变量都是window对象的属性)。代码写出时就定义好了做用域,好比谁嵌套在谁里面。bash
注意for、if、else 是不能创造做用域的。闭包
// 只有一个popup函数级做用域,变量i、j、k在整个popup函数体内都是全局的
function popup() {
var i = 0;
if(true) {
var j = 0;
for(var k = 0; k < 3; k++) {
console.log(k); // 分别输出 0 1 2
}
console.log(k); // 输出3
}
console.log(j); // 输出0
}
复制代码
// 虽然此处引用a在声明a以前,但并未报错,即变量提高
console.log(a); // undefined
var a = 1;
// 上面代码可间接理解成以下逻辑
var b;
console.log(b); // undefined
b = 1;
// 一函数体内任意位置声明的函数或变量,都会被提高到函数体内最顶层
// 形参不会被从新定义,且同名的优先级 函数>形参>变量
var c = 1;
function run(x, y, z, w) { // 形参会被添加到函数的做用域中
console.log(c); // 内部有c的声明,因此输出'undefined',而不是1
var c = 'runnerman';
console.log(c); // 输出'runnerman'
console.log(x); //
var x = 5; // x=5被执行
function x() { // 被提高到了做用域顶部
console.log('x coming');
}
console.log(x); // 5
console.log(y); // parma2
var y = 10; // var y被忽略,y=10被执行
console.log(y); // 10
console.log(z); // param3
var z = function z() { // var z被忽略
console.log('z coming');
};
console.log(z); //
console.log(w); //
function w() {
console.log('w coming');
};
w = 20;
console.log(w); // 20
}
run('param1', 'param2', 'param3', 'param4');
/* 输出:
undefined
runnerman
function x() {
console.log('x coming');
}
5
param2
10
param3
function z() {
console.log('z coming');
}
function w() {
console.log('w coming');
}
20
*/
// 但如此使用let,便会报错
console.log(d); // Uncaught ReferenceError: d is not defined
let d = 1;
var e = 90;
var e = 900; // 能够,会被覆盖
// let不可重复声明
let f = 90;
let f = 900; // Uncaught SyntaxError: Identifier 'f' has already been declared
复制代码
for(var i=0;i<3;i++) {
setTimeout(function() {
console.log(i)
}, 1000)
}
// 结果:3,3,3
for(let j=0;j<3;j++) {
setTimeout(function() {
console.log(j)
}, 1000)
}
// 结果:0,1,2
复制代码
function fight() {
console.log(this) // Window
}
// 此处至关于Window调用了fight
fight()
var ironman = {
name: "Tony Stark",
fly: function() {
console.log(this.name + ' is flying') // this === ironman
}
}
ironman.fly() // Tony Stark is flying
function Superhero(name, power) {
this.name = name // this指向spiderman
this.power = power
//return this
}
// 首先new字段会建立一个空的对象,而后调用apply()函数,将this指向这个空对象
var spiderman = new Superhero('spiderman', 'jumping')
复制代码
var name = 'anyone', age = '30'
var ironman = {
name: "Tony Stark",
imAge: this.age,
run: function(skill) {
console.log(this.name + " is " + this.age + ', ready to ' + skill)
}
}
// ironman为全局变量,此时的this指向为Window
console.log(ironman.imAge) // 30
// 此时函数中的this指向ironman
ironman.run('fly') // Tony Stark is undefined, ready to fly
// call,apply,bind第一个参数都是this指向的对象
// call和apply若是第一个参数指向null或undefined时,那么this会指向Window对象
// call,apply都是改变上下文中的this,并当即执行;bind方法可随后手动调用
var starlord = {name: "dude", age: 13}
ironman.run.call(starlord, "dance") // dude is 13, ready to dance
ironman.run.apply(starlord, ["dance"]) // dude is 13, ready to dance
ironman.run.bind(starlord, "dance")() // dude is 13, ready to dance
复制代码
var globalObject = this;
var foo1 = (() => this); // 箭头函数:声明时已肯定了指向
var foo2 = function() { return this }; // 运行时才能肯定指向
console.log(foo1() === globalObject); // true
console.log(foo2() === globalObject); // true
var obj = {foo1: foo1, foo2: foo2};
console.log(obj.foo1() === globalObject); // true
console.log(obj.foo2() === obj); // true 指向调用其的对象
console.log(foo1.call(obj) === globalObject); // true
console.log(foo2.call(obj) === obj); // true
foo1 = foo1.bind(obj);
foo2 = foo2.bind(obj);
console.log(foo1() === globalObject); // true
console.log(foo2() === obj); // true
复制代码
function Printer() {
var count = 0;
this.print = function() { // 引用了函数局部变量count
count++;
console.log(count);
};
}
var p = new Printer();
p.print(); // 1 至关于从外部引用了函数内部的局部变量
p.print(); // 2 此处也说明了count一直在内存中,并未在print调用后清除,缘由正是由于count被函数外部所引用的关系
复制代码
通常用于:app
注意:滥用闭包会致使函数中的变量都被保存在内存中,内存消耗很大,致使网页性能问题,IE中可能致使内存泄露。因此在退出函数以前,最好将不使用的局部变量所有清除。ide