做用域能夠理解爲環境上下文,包含了變量、函數聲明、參數等。在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