变数、作用域、闭包
var, let, 以及 const 有什么差别
- 在作用域上,var 可以是全域、也可以是以函式作为范围;let 与 const 则是以区块作为范围。
- 在宣告上,var 可以被重复宣告,但是 let 与 const 则不行。
- 在提升上,var 宣告的变数会自动初始化值为undefined,因此在宣告前就使用变数,不会出现错误,而会是undefined ;但是let 与const 宣告的变数则不会自动初始化,而是会进到暂时死区(TDZ),因此在let 与const 宣告变数前使用该变数,会出现错误。
- let 与 const 在绝多数面向都是一样,两者的一大区别在于,用 let 宣告的变数可以重新赋值,但是用 const 的不行。
什么是提升 (Hoisting)?
- Hoisting :JavaScript 编译阶段将变数和函式的宣告存入记忆体的概念。
- var 提升 (hoisting):提早呼叫的结果会是 undefined
- let / const :let 在提升变数到区块作用域(block scope) 范围时,并不会初始化此变数,这个状态可以称之为uninitialized,也有另一个常见的说法是,let 和const 定义的变数目前存在于暂时死区(TDZ,Temporal dead zone
- function declarations
- 用 var 宣告的foo 函式,在宣告前使用时,当时值会是 undefined,因此呼叫undefined 会报错。
- 用 let 宣告的 foo 函式,在宣告前使用时,此时 foo 在暂时死区,因此呼叫 foo 会报错。
foo(); // 1
function foo() {
console.log(1);
}
foo(); // Uncaught TypeError: foo is not a function
var foo = function () {};
foo(); // Uncaught ReferenceError: foo is not defined
let foo = function () {};
Javascript 的作用域 (Scope) 与作用域链 (Scope Chain) 是什么?
作用域指的是当前正在执行的上下文,也就是程式目前所处的情境。在这个情境内,我们可以轻松地存取各种值(value)或表达式(expression)。
- 全域(Global Scope):
- 块级作用域 (Block Scope):只有 let 和 const 定义的变数会属于块级作用域,如果是 var 定义的变数会是只有函式作用域。
- 函式作用域 (Function Scope):由函式所创建的作用域
什么是作用域链 (Scope Chain):当 JavaScript 使用每一个变数的时候,会先尝试在当前作用域中寻找该变数,若在当前的作用域找不到该变数,会一直往父层作用域寻找,直到全局作用域还是没找到,就会直接报错,这一层一层的关系,就是作用域链。
什么是闭包 (Closure)?
什么是闭包:闭包就是内部函式能够取得函式外部的变数,并且记住这个变数。因为能够记住这个外部变数,闭包很常被用来做状态保存。
闭包的应用 1 — 状态保存
function useState(initialState) {
let state = initialState;
function getState() {
return state;
}
function setState(updatedState) {
state = updatedState;
}
return [getState, setState];
}
const [count, setCount] = useState(0);
count(); // 0
setCount(1);
count(); // 1
setCount(500);
count(); // 500
- 闭包的应用 2 — 快取
function memoize(fn) {
// 声明一个 cache 物件,透过 cache 来放快取的东西
// 因为闭包的缘故,下面回传的函式可以存取到这个 cache 变数
const cache = {};
// 透过扩展运算符,拿到引数
return (...args) => {
// 将引数当作快取的 key
const key = JSON.stringify(args);
// 查看现在的快取有没有这个 key,有的话就不用再次运算,直接回传
if (key in cache) {
return cache[key];
} else {
// 没有的话,就把收到引数带入,运算出结果
const val = fn(...args);
// 把结果放入快取,下次有同样的 key 就不用重新运算
cache[key] = val;
return val;
}
};
}
- 闭包的应用 3 — 模拟外部无法直接访问的变量。
var counter = (function () {
var privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function () {
changeBy(1);
},
decrement: function () {
changeBy(-1);
},
value: function () {
return privateCounter;
},
};
})();
console.log(counter.value()); // logs 0
counter.increment();
counter.increment();
console.log(counter.value()); // logs 2
counter.decrement();
console.log(counter.value()); // logs 1