- ==1.函数的用法==
- 复用代码(略)
- 把函数作为数据/值使用
- 把函数作为命名空间使用
- ==2. 作用域|变量提升 ==
- 变量作用域
- 变量声明提升
- 做题思路
- ==3. 闭包==
- 闭包
函数的用法
- 用法1:复用代码。可以定义函数、调用函数,复用函数,这是函数的本职功能,也是函数的语法特性。
- 用法2:把函数作为数据/值使用
- 用法3:把函数作为命名空间使用
把函数作为数据/值使用
函数也是数据/值。这意味着我们可以把函数赋值给变量、赋值给对象属性、赋值给数组元素、赋值给形参等
示例: 函数名本质上就是一个变量
function sum(a,b){return a + b}
把函数赋值给另一个变量
let s = sum
s(2,3)//5
把函数赋值给对象的属性
let obj = {name:'张三',age:19}
obj.s = sum
obj.s(3,3)
把函数赋值给数组
let arr = [1,2,sum]
console.log(arr[2](arr[0], arr[1]));
把函数作为命名空间使用
当把函数作为命名空间使用时,可以确保函数体内的变量不会污染全局命名空间。
有名函数
let maxValue = 100
function max(a,...aaa) {//只定义了全局变量max
let maxValue = -Infinity
for (n of aaa) {
if (n > maxValue) {
maxValue = n
}
}
return maxValue
}
console.log(window.max);//我不想添加到window上,可以使用自执行函数
自执行函数(推荐)
;(function(a,...aaa){
let maxValue = -Infinity
for (n of aaa) {
if (n > maxValue) {
maxValue = n
}
}
console.log(maxValue);
return maxValue
}(33,4,555))
// console.log(window.max)
====2. 作用域与变量提升 ====
变量作用域
复习
let
定义变量的关键字,它是临时性赋值。
const
定义常量的关键字,它是永久性赋值。声明常量约定全部使用大写字母命名标识符。
var
ES6之前定义变量的关键字,它是临时性赋值。
变量作用域指定义变量的区域。 变量在哪个区域定义,就在哪个区域发生作用。变量作用域决定了变量的可访问性。在当前定义区域内,变量可以被访问到,在这个区域以外,变量不能被访问到。
示例:全局作用域 VS 局部作用域
<script>
var a = 10 //变量a拥有全局作用域
function fn(){
console.log(a) //输出10,变量a在全局任何位置都可以访问
var b = 20
console.log(b) //输出20,变量b定义在函数fn内,故拥有局部作用域,在函数fn内可以访问
}
console.log(b) // 报错。变量b是局部变量,无法从函数fn外面访问。
</script>
有两种类型的作用域:
- 全局作用域:局部作用域之外的区域就是全局作用域。
- 局部作用域:局部作用域指函数作用域和块级作用域。这两种局部作用域使用不同的关键词定义:
- 函数作用域:指在
函数体
内,使用关键词var
定义的变量,会创建变量的函数作用域,也就是说变量只能在当前函数体内才可以访问。注:变量定义在if或for创建的代码块内,是不会创建函数作用域的。 - 块级作用域: 指在
大括号
包裹的区域内,使用关键词let
或const
定义的变量,会创建块级作用域,只能在大括号内才可以访问。
- 函数作用域:指在
示例:函数作用域
<script>
var a = 10
function fn(){
var a = 20
console.log(a) //20 这个a定义在函数内,是局部变量
}
console.log(a) //10 这个a定义在函数外,是全局变量
</script>
示例:块级作用域
<script>
let a = 10
if(a === 10){
let a = 20//let在这里创建了局部作用域,故变量b是局部变量。
var b = 30 //var只能创建函数作用域,无法创建块级作用域。故在这里创建的变量b是全局变量
console.log(a) //20 这个a定义在函数内,是局部变量
}
console.log(b) //10 因为b定义在全局,故b可以读取到10
console.log(a) //10 这个a定义在函数外,是全局变量
</script>
变量声明提升
JavaScript 提升是指解释器似乎在执行代码之前将函数、变量或类的声明移动到其作用域顶部的过程。
示例:var
console.log(x === undefined); // true
var x = 3;
(function() {
console.log(x); // undefined
var x = 'local value';
})();
等价于
var x;
console.log(x === undefined); // true
x = 3;
(function() {
var x;
console.log(x); // undefined
x = 'local value';
})();
示例:let 与 const的临时性死区
从块开始到声明被处理,变量都处于“时间死区”
console.log(x); // ReferenceError
const x = 3;
console.log(y); // ReferenceError
let y = 3;
如果 const x = 2 声明根本没有提升,那么 console.log(x) 语句应该能够从上层作用域读取 x 值。然而,因为 const 声明仍然“污染”了它定义的整个范围,console.log(x) 语句改为从 const x = 2 声明中读取 x,它尚未初始化,并抛出 ReferenceError。
尽管如此,将词法声明描述为非提升可能更有用,因为从功利主义的角度来看,这些声明的提升不会带来任何有意义的特征。
变量声明提升是关于函数作用域的一个重要问题。先看一个的例子:
//请问console.log()第一次输出的是什么?
var n = 123
function fn(){
console.log(n) //?
var n = 456
console.log(n)
}
fn()
您可能会想当然第认为第一次console.log()输出的是123,也就是全局变量n的值。但事实并非如此,第一个console.log()实际上输出的是undefined
。因为在函数体内:变量n也有定义,故局部变量n会覆盖全局变量n。尽管在函数体内,第一次调用局部变量n的时候,n还没有定义,但n实际上已经存在于本地空间(函数体内)了,这种特殊现象叫做提升(hoisting) 。
提升(hoisting) :指当JS执行过程进入新函数时,新函数内的的所有变量声明(如var n)都会被提升到当前作用域的顶部。
示例:模拟var变量声明提升
<script>
/**********************
var n //变量声明被提升至顶部
*********************/
console.log(n) // undefined(在这里可以读取到n的原因就在于变量声明提升)
var n = 123
function fn(){
/*****变量声明被提升至顶部******
var n
*************************/
console.log(n) //undefined(在这里可以读取到n的原因就在于变量声明提升)
var n = 456
console.log(n) //456
}
fn()
</script>
示例: 模拟函数提升
/********整个函数被提升到顶部**********
function fn(){
console.log('hello world')
}
*******************************/
fn() // 'hello world' (在这里可以读取到fn()的原因就在于函数提升)
function fn(){
console.log('hello world')
}
函数声明也会创建变量,所以函数声明也会发生变量提升,与变量声明提升不同的是,整个函数,包括函数声明与函数体都会被提升至当前作用域顶部。
做题思路
- 要知道只有一个全局作用域。
- 你只需确定局部作用域有几个;(局部作用域有两种:var声明会创建函数作用域,let和const声明会块级作用域。)
- 预解析:在每个作用域内做预解析;(
var a
和function(){}
一定会提升,let
和const
提升会报错,所以你就理解为let和const不提升) - 逐行解析: 逐行读取代码
- 优先级:①如果出现变量名和函数名相同的情况,那么函数a的优先级高于变量a的优先级;②如果局部变量和全局变量同名,那么局部变量覆盖全局变量。
示例
示例