JS高级(一)、 变量作用域、JS垃圾回收机制;闭包;函数参数;箭头函数(this);解构赋值

一、作用域

1. 局部作用域

  • 函数作用域:在函数内部声明的变量只能在内部被访问,外部无法直接访问
function fn () {
const num = 10
console.log(num);
}
console.log(i); // 报错
  • 块作用域:在JS中,用{}包裹的代码称为代码块
for (let i = 0; i < 3; i++) {
    var name = 'tom'
    console.log(i);
}
console.log(name); // name var声明的变量可以外部访问
console.log(i); //报错

总结:
(1). const与let声明的变量会产生块作用域,var声明的变量不产生块作用域;
(2). 不同代码块之间的变量也无法相互访问;

2. 全局作用域

全局作用域:标签内或者单独的js文件中;
在全局作用域内容声明的变量可被其他作用域访问到。

 // 全局作用域
 const age = 18
 function fn () {
     console.log(age);
 }

尽可能少的声明全局变量,防止全局变量污染。

3. 作用域链

  • 作用域链本质上是底层的变量查找机制
  • 作用域链的查找规则
    • 优先从当前作用域查找变量
    • 查找不到则会依次逐级查找父级作用域直到全局作用域
  • 子作用域能够访问父作用域,父级作用域无法访问子级作用域
    在这里插入图片描述
    作用域链分别为 g()—> f()---->global

4. JS垃圾回收机制

  垃圾回收机制(Garbage Collection) 简称GC。JS中内存的分配和回收都是自动完成的,内存不使用时,会被GC回收。

(1) 内存泄漏

程序中分配的内存由于某些原因未被释放或者无法释放的情况,就叫内存泄漏。

(2) 内存生命周期:

  • 内存分配:声明变量、函数、对象时,系统会自动分配内存
  • 内存使用:读写内存,即使用变量、函数
  • 内存回收:由GC回收不再使用的内存

  注意:全局变量一般不会回收(关闭页面时才回收),一般情况下局部变量的值不用了,会被自动回收掉

(3) 垃圾回收算法:引用计数法、标记清除法

  • 引用计数法:定义内存不再使用,就是看一个对象是否有指向它的引用
const arr = [1,2,3,4]
arr = null

在这里插入图片描述
存在的问题:如有两个对象相互引用,尽管已不再使用,GC也不会回收,进而导致内存泄漏。

  • 标记清除法:从根部出发定时扫描内存中的对象。凡是能从根部出发达到的对象,都是还需使用的。凡是从根部触发无法到达的对象,则标记为不再使用,进行回收。

在这里插入图片描述
在这里插入图片描述

5. 闭包

闭包(closure) : 内层函数+外层函数的变量。内层函数得使用了外层函数的变量才能叫闭包。

function outer () {
    const num = 10
    function fn () {
        console.log(num);
    }
    fn()
}
outer() // 打印10

// 一般这样写
function outer () {
    const num = 10
    function fn () {
        console.log(num);
    }
 return fn
}
const fun = outer() // outer() = fn
fun() // 打印10

闭包作用:封闭数据,提供操作。让外部也可访问函数内部的变量(有点儿像Java的get、set方法)

案例:做一个统计函数调用次数

let count = 0
function fn () {
    count++
    console.log(`函数调用了${count}`);
}
fn() // 函数调用了1次
fn() // 函数调用了2次

这样的缺点是,count是全局变量,可能会被别人修改,改写成闭包的形式:

function outer () {
    let count = 0
    function fn () {
        count++
        console.log(`函数调用了${count}`);
    }
    return fn
}
const fun = outer()
fun() // 函数调用了1次

实现数据私有,无法直接修改count.

闭包缺点:可能引起内存泄漏。

  fun的作用域是全局作用域,除非页面关闭,否则不会被回收。进而导致count也不会被GC回收。引起内存泄漏。

6. 变量提升 (了解)

变量提升仅存在于var声明的变量,let与const声明的变量不存在变量提升。

  // 变量提升,了解即可。属于JS的一个缺陷
  console.log(num); // undefined
  var num = 10

控制台不会报错,而是打印undefined。因为num进行了变量提升
变量提升:

  • 先把var变量提升到当前作用域的最前面,然后依次执行代码
  • 只提升变量声明,不提升变量赋值

上述代码相当于

  var num
  console.log(num); // undefined
  num = 10

二、函数进阶

1. 函数提升

  • 把所有函数声明提升到当前作用域前面
  • 只提升函数声明,不提升函数调用

与变量提升类似,可以解释为什么能在函数声明之前进行调用

//函数调用
fn()
function fn () {
    console.log('具名函数提升');
}

相当于

function fn () {
    console.log('具名函数提升');
}
//函数调用
fn()
  • 函数表达式不存在函数提升
fun() // 报错
var fun = function () {
    console.log('函数表达式不会提升');
}

发生了var的变量提升,变量只提升声明,不提升赋值。等价于:

var fun
fun()
fun = function () {
    console.log('函数表达式不会提升');
}

2. 函数参数

需求:写一个求和函数,不管用户传入几个参数,都要把和求出来

  • 动态参数arguments(es5):只存在于函数内的伪数组
function sum () {
    let sum = 0
    for (let i = 0; i < arguments.length; i++) {
        sum += arguments[i]
    }
    return sum
}
console.log(sum(1, 2));   // 3
console.log(sum(1, 2, 3));// 6

arguments是函数内部内置的伪数组,包含调用函数时传入的所有实参

  • 剩余参数–推荐使用:获取剩余参数,是个真数组
    剩余参数:三个点...和一个紧跟着的具名参数(就是变量名)
function sum2 (...arg) {
    console.log(arg);
    let sum = 0
    for (let i = 0; i < arg.length; i++) {
        sum += arg[i]
    }
    return sum
}
console.log(sum2(1, 2));
console.log(sum2(1, 2, 3));

在这里插入图片描述

剩余参数应在函数形参最后的位置来获取多余的实参

function fn (形参1,...arg) {
	...
}

拓展:展开运算符...,可将一个数组进行展开

const arr = [1, 2, 3, 4, 5]
console.log(...arr);//1 2 3 4 5

应用场景:求数组最大(小)值,合并数组

// 求数组最大值、最小值
const num1 = [34, 23, 1, -20]
console.log(Math.max(...num1));
console.log(Math.min(...num1));

// 合并数组
const arr1 = ['red', 'blue', 'pink']
const arr2 = ['skyblue']
const arr3 = [...arr1, ...arr2] // ['red', 'blue', 'pink', 'skyblue']

剩余运算符:把多个独立的合并到一个数组中
展开运算符:将一个数组分隔,将各个项作为分离的参数传给函数

3. 箭头函数

3.1 基本语法

函数表达式:(回顾,这里已经忘了)

const fn = function () {
    console.log('匿名函数表达式');
}

语法1:基本写法

const fn1 = () => {
    console.log('箭头函数基本写法');
}

语法2: 只有一个参数时,可以省略小括号

const fn2 = x => {
    return x + 1;
}

语法3:如果函数体只有一行代码,可写到一行,且不用写return

// 函数体只有一行,省略{}
const fn3 = x => console.log(x);
fn3(5); // 5
// 有返回值的不用写return
const fn4 = (x, y) => x + y
console.log(fn4(1, 2)); // 3

语法4:返回对象; 要是只写{},则分不清表达的是函数体还是对象字面量,所以要再加上()

const fn5 = uname => ({ name: uname })
console.log(fn5('tom')); // {name:'tom'}

3.2 箭头函数参数

与普通函数的参数使用方式一样,也可以使用剩余参数。唯一的区别是,没有动态参数arguments。
使用剩余参数:

const fparam = (...arr) => {
    console.log(...arr);
}
fparam(1, 2) // 1 2
fparam(1, 2, 3) // 1 2 3

3.3 箭头函数this

箭头函数不会创建自己的this。箭头函数的this是沿用作用域链上一层的this。
就是沿着作用域链一层层找,找到一个父级有this,则箭头函数的this沿用该父级的this。
正常情况下,谁调用this,this指向谁

console.log(this); // window
// 普通函数
function fn () {
    console.log('普通函数');
    console.log(this); // window
}
fn() // 相当于 window.fn()
// 对象方法里的this
let obj = {
    name: 'tom',
    sayHi: function () {
        console.log('sayHi');
        console.log(this); //打印出来是obj,
    }
}
obj.sayHi() 

箭头函数:

const fn1 = () => {
    console.log('箭头函数');
    console.log(this); // 打印window;这个window是上一层作用域(script)this的指向.而非调用fn1()的那个window
}
fn1() 

对象方法里箭头函数的this
在这里插入图片描述
(有没有this,我的判断指标是在此处能不能console.log(this))

嵌套箭头函数里的this:

const obj3 = {
    uname: 'pink老师',
    sayHi: function () {
        console.log(this)  // obj
        let i = 10
        const count = () => {
            console.log(this)  // obj 是sayHi function作用域this的指向
        }
        count()
    }
}
obj3.sayHi()

DOM事件回调函数中,不推荐使用箭头函数

  // DOM节点
  const btn = document.querySelector('.btn')
  // 箭头函数,此时this指向全局的window
  btn.addEventListener('click', () => {
      console.log(this);
  })
  // 普通函数 此时this指向DOM对象
  btn.addEventListener('click', function(){
      console.log(this);
  })

4 解构赋值

4.1 数组解构

数据解构就是将数组的单元值快速批量赋值给一系列变量的简洁语法
语法:const [变量1,变量2,变量3] = [单元值1,单元值2,单元值3]
单元值依次赋给对应位置的变量;

// 1. 数组解构
const arr = [100, 60, 80]
const [max, min, avg] = arr
// 或者这么写。同时将数组单元值100 60 80 赋给变量max,min,avg
// const [max, min, avg] = [100, 60, 80]
console.log(max); // 100
console.log(min); // 60
console.log(avg); // 80

典型应用:交换2个变量

let a = 1
let b = 2;      //必须有分号
[b, a] = [a, b]
console.log(a) // 1
console.log(b) // 2

JS必须加分号的两种情况:立即执行函数,数组解构

4.2 变量与单元值数量不对等的情况

  • 变量多,单元值少;多余的变量是undefined
const [a, b, c, d] = ['小米', '苹果', '华为']
console.log(a); // 小米
console.log(b); // 苹果
console.log(c); // 华为
console.log(d); // undefined

可设置默认值防止undefined

const [a = '小米', b = '苹果'] = ['小米', '苹果', '华为']
console.log(a); // 小米
console.log(b); // 苹果
  • 变量少,单元值多
const [a, b] = ['小米', '苹果', '华为']
console.log(a); // 小米
console.log(b); // 苹果

可用剩余参数接收多余的单元值

const [a, b,...c] = ['小米', '苹果', '华为','vivo','格力']
console.log(a); // 小米
console.log(b); // 苹果
console.log(c); // ['华为','vivo','格力']
  • 按需导入,忽略某些值
const [a, , c, d] = ['小米', '苹果', '华为', '格力']
console.log(a); // 小米
console.log(c); // 华为
console.log(d); // 格力
  • 支持多维数组
const [a, b, c] = ['小米', '苹果', ['华为', '格力']]
console.log(a); // 小米
console.log(b); // 苹果
console.log(c); // ['华为', '格力']

4.3 对象解构

  • 语法:{批量声明的变量}=对象
  • 对象属性的值将被赋值给与属性名相同的变量;
  1. 将对象属性赋值给变量,变量名与对象属性名需一致,若不一致则为undefined
let pig = {
    name: '佩奇',
    age: 6
}
const { name, age } = pig
// 等价于
const name = pig.name
const age = pig.age
// 
const { uname, age } = pig // uname与对象中没有对应是属性名,所以值是undefined

2、对象的属性名与外部变量名重名时,需要改名
语法:旧变量名=新变量名

const { name: pName, age: pAge } = pig

3、数组对象

const pigFam = [
    {
        name: '佩奇',
        age: 6
    },
    {
        name: '乔治',
        age: 4
    }
]
// 解构方式:
const [{ name, age }, { name: zName, age: zAge }] = pigFam

4、多级对象解构

let dog = [{
    name: '佩奇',
    family: {
        mother: '狗妈妈',
        father: '狗爸爸',
        brother: '乔治',
    },
    age: 10
}]
// 小技巧:按着这个对象的结构来先[],里面是{}
const [{ name, family: { mother, father, brother }, age }] = dog

案例:后台传递过来的数据,需要把data选出当做参数传递给 函数

// 1. 这是后台传递过来的数据   
const msg = {
    "code": 200,
    "msg": "获取新闻列表成功",
    "data": [
        {
            "id": 1,
            "title": "5G商用自己,三大运用商收入下降",
            "count": 58
        },
    ]
}
// 需求2
function render ({ data }) {
    console.log('函数内部', data);
}
render(msg)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值