目录
(3)call()、apply()、bind()等方法不能改变箭头函数中this的指向
(4)箭头函数不能作为构造函数使用 (在JavaScript中,用new关键字来调用的函数,称为构造函数,构造函数首字母一般大写)
一、作用域
1.局部作用域分为函数作用域和块作用域
(1)函数作用域
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
// 声明 counter 函数
function counter(x, y) {
// 函数内部声明的变量
const s = x + y
console.log(s) // 18
}
// 设用 counter 函数
counter(10, 8)
// 访问变量 s
console.log(s)// 报错
-
函数内部声明的变量,在函数外部无法被访问
-
函数的参数也是函数内部的局部变量
-
不同函数内部声明的变量无法互相访问
-
函数执行完毕后,函数内部的变量实际被清空了
(2)块作用域
在 JavaScript 中使用 {}
包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问
{
// age 只能在该代码块中被访问
let age = 18;
console.log(age); // 正常
}
// 超出了 age 的作用域
console.log(age) // 报错
(3)额外
-
let
声明的变量会产生块作用域,var
不会产生块作用域 -
const
声明的常量也会产生块作用域 -
不同代码块之间的变量无法互相访问
-
推荐使用
let
或const
2.全局作用域
// 此处是全局
function sayHi() {
// 此处为局部
}
// 此处为全局
全局作用域中声明的变量,任何其它作用域都可以被访问,如下代码所示:
// 全局变量 name
const name = '小明'
// 函数作用域中访问全局
function sayHi() {
// 此处为局部
console.log('你好' + name)
}
尽可能少的声明全局变量,防止全局变量被污染
二、作用域链
作用域链本质上是底层的变量查找机制,在函数被执行时,会优先查找当前函数作用域中查找变量,如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域,如下代码所示:
// 全局作用域
let a = 1
let b = 2
// 局部作用域
function f() {
let c
// let a = 10;
console.log(a) // 1 或 10
console.log(d) // 报错
// 局部作用域
function g() {
let d = 'yo'
// let b = 20;
console.log(b) // 2 或 20
}
// 调用 g 函数
g()
}
console.log(c) // 报错
console.log(d) // 报错
f();
总结:
-
嵌套关系的作用域串联起来形成了作用域链
-
相同作用域链中按着从小到大的规则查找变量
-
子作用域能够访问父作用域,父级作用域无法访问子级作用域
三、闭包
闭包是一种比较特殊和函数,使用闭包能够访问函数作用域中的变量。从代码形式上看闭包是一个做为返回值的函数,如下代码所示:
// 1. 闭包 : 内层函数 + 外层函数变量
function outer() {
const a = 1
function f() {
console.log(a)
}
f()
}
outer()
闭包 = 内层函数 + 外层函数的变量
(1)什么是闭包
- 闭包就是能够读取其他函数内部变量的函数。
- 由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成 " 定义在一个函数内部的函数" 。
- 所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
(2)闭包的作用
-
封闭数据,实现数据私有,外部也可以访问函数内部的变量
-
闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来
-
使用闭包可以延长局部变量的生命周期,不让局部变量使用后立即释放,被删除
(3)闭包可能引起的问题
-
内存泄漏
(4)读取全局变量
// 当声明变量 i 在outFn 函数外面时,输出结果为:1,2,3,4
var i=0;
function outerFn(){
return function innerFn(){
i++;
console.log(i);
}
}
var fn1 = outFn();
fn1();
fn1();
var fn2 = outFn();
fn2();
fn2();
(5)读取局部变量
// 当声明变量 i 在innerFn函数里面时,输出结果为:1,1,1,1
// (当函数重复调用的时候,其内部的局部变量会被重新声明)
function outerFn(){
return function innerFn(){
var i=0;
i++;
console.log(i);
}
}
var fn1 = outFn();
fn1();
fn1();
var fn2 = outFn();
fn2();
fn2();
/*当声明变量 i 在innerFn函数外面,outerFn函数里面时,输出结果为:1,2,1,2
因为声明变量是在outFn函数作用域里,在outFn的子作用域里 i 被使用,所以这时的 i 为自由变量,这时得作用域嵌套环境叫做闭包,形成了闭包。*/
function outerFn(){
var i=0;
return function innerFn(){
i++;
console.log(i);
}
}
var fn1 = outFn(); i 声明了一次
fn1(); i 自增一次,变为1
fn1(); i 自增一次 变为2
var fn2 = outFn(); i 重新被声明 为0
fn2(); i 自增一次 为1
fn2(); i 自增一次 为2
四、变量提升
(1)var
// 访问变量 str
console.log(str + 'world!');//undefine
// 声明变量 str
var str = 'hello ';
(2)let
// 访问变量 str
console.log(str + 'world!');//报错
// 声明变量 str
let str = 'hello ';
总结:
-
变量在未声明即被访问时会报语法错误
-
变量在声明之前即被访问,变量的值为
undefined
-
let
声明的变量不存在变量提升,推荐使用let
-
变量提升出现在相同作用域当中
-
实际开发中推荐先声明再访问变量
(3)函数提升
- 函数声明会被提升
var a = true;
foo();
function foo() {
if(a) {
var a = 10;
}
console.log(a);
}
这个例子最终的答案是 undefined
。下面是这段代码实际会被 JavaScript 执行的样子:
function foo() {
var a;
if(a) {
a = 10;
}
console.log(a);
}
var a;
a = true;
foo();
在 JavaScript 中没有块级作用域,所以 var a = 10;会被 JavaScript 分为两步中的 var a; 会被提升到函数作用域中的最顶端,声明了一个局部变量 a,在 foo(...) {} 的函数作用域中,这个重名局部变量 a 会屏蔽全局变量 a,换句话说,在遇到对 a 的赋值声明之前,在 foo(...) {},a 的值都是 undefined!所以一个 undefined 的 a 进入不了 if(a) {...} 中,所以最后被打印出来的是 undefined
- 函数表达式却不会被提升
var a = true;
foo();
var foo = function() {
if(a) {
var a = 10;
}
console.log(a);
}
这里会抛出 TypeError,而不是 ReferenceError。
同常量提升,代码会被解析为:
var a;
var foo;
a = true;
foo();
foo = function() {
if(a) {
var a = 10;
}
console.log(a);
}
五、箭头函数
(1)箭头函数 this
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this
// 以前this的指向: 谁调用的这个函数,this 就指向谁
// console.log(this) // window
// // 普通函数
// function fn() {
// console.log(this) // window
// }
// window.fn()
// // 对象方法里面的this
// const obj = {
// name: 'andy',
// sayHi: function () {
// console.log(this) // obj
// }
// }
// obj.sayHi()
// 2. 箭头函数的this 是上一层作用域的this 指向
// const fn = () => {
// console.log(this) // window
// }
// fn()
// 对象方法箭头函数 this
// const obj = {
// uname: 'pink老师',
// sayHi: () => {
// console.log(this) // this 指向谁? window
// }
// }
// obj.sayHi()
(2)箭头函数继承来的this指向永远不会改变
(3)call()、apply()、bind()等方法不能改变箭头函数中this的指向
(4)箭头函数不能作为构造函数使用 (在JavaScript中,用new关键字来调用的函数,称为构造函数,构造函数首字母一般大写)
(5)箭头函数没有自己的arguments
六、解构赋值
(1)数组解构
数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法
// 普通的数组
let arr = [1, 2, 3]
// 批量声明变量 a b c
// 同时将数组单元值 1 2 3 依次赋值给变量 a b c
let [a, b, c] = arr
console.log(a); // 1
console.log(b); // 2
console.log(c); // 3
总结:
-
赋值运算符
=
左侧的[]
用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量 -
变量的顺序对应数组单元值的位置依次进行赋值操作
-
变量的数量大于单元值数量时,多余的变量将被赋值为
undefined
-
变量的数量小于单元值数量时,可以通过
...
获取剩余单元值,但只能置于最末位 -
允许初始化变量的默认值,且只有单元值为
undefined
时默认值才会生效
(2)对象解构
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法
// 普通对象
const user = {
name: '小明',
age: 18
};
// 批量声明变量 name age
// 同时将数组单元值 小明 18 依次赋值给变量 name age
const {name, age} = user
console.log(name) // 小明
console.log(age) // 18
总结:
-
赋值运算符
=
左侧的{}
用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量 -
对象属性的值将被赋值给与属性名相同的变量
-
对象中找不到与变量名一致的属性时变量值为
undefined
-
允许初始化变量的默认值,属性不存在或单元值为
undefined
时默认值才会生效
七、forEach遍历数组
forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数
注意:
1.forEach 主要是遍历数组
2.参数当前数组元素是必须要写的, 索引号可选
// forEach 就是遍历 加强版的for循环 适合于遍历数组对象
const arr = ['red', 'green', 'pink']
const result = arr.forEach(function (item, index) {
console.log(item) // 数组元素 red green pink
console.log(index) // 索引号
})
八、filter筛选数组
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素
const arr = [10, 20, 30]
// 返回的符合条件的新数组
const newArr = arr.filter(item => item >= 20)
console.log(newArr)//[20,30]