Skip to content

00 原型5条规则

  1. 所有的引用类型(数组,对象,函数),都具有对象的特性,可以自由扩展属性
  2. 所有的对象,都有一个__proto__的属性(隐式原型),属性值是一个普通的对象
  3. 所有的函数,都有一个prototype属性(显示原型),属性值也是一个普通对象
  4. 所有对象的隐式原型(proto), 指向它的构造函数的显示原型(prototype)
  5. 当试图得到某个对象的属性时,如果在这个对象本身没有这个属性,那么会去它的__proto__中寻找

上述的所有函数,不包含箭头函数 () => {}, 箭头函数没有prototype

javascript
原型五条规则 重要!!! 记住,背!!!1000块
// 语法糖
// 甜甜的,写了前面这种方式,就不想写后面这种方式了
const obj = {}  // const obj = new Object()
const arr = []  // const arr = new Array()

//! 1. 所有的引用类型(数组,对象,函数),都具有对象的特性,可以自由扩展属性
let obj = {}  // obj.a = 100
let arr = []  // arr.a = 200
function fn() {} // fn.a = 500

//! 2. 所有的对象,都有一个__proto__的属性(隐式原型),属性值是一个普通的对象  
// (浏览器中表现为[[prototype]])
console.log(obj.__proto__)
console.log(arr.__proto__)
console.log(fn.__proto__)

//! 3. 所有的函数,都有一个prototype属性(显示原型),属性值也是一个普通对象
// (这个属性值是一个指针,指向原型对象)
console.log(fn.prototype)

//! 4. 所有对象的隐式原型(__proto__), 指向它的构造函数的显示原型(prototype)
// 指向 ==> 理解为等于
// 构造函数 ==> 函数名大写,可以通过这个函数,创建很多实例(具体的例子)对象

console.log(obj.__proto__ === Object.prototype)
console.log(arr.__proto__ === Array.prototype)
console.log(fn.__proto__ === Function.prototype)

function Person(name, age) {
    this.name = name
    this.age = age
}
const p = new Person('练练', 18)
console.log(p)
console.log(p.__proto__ === Person.prototype)

//! 5. 当试图得到某个对象的属性时,如果在这个对象本身没有这个属性,那么会去它的__proto__中寻找

// const arr = [1, 2, 3]
// arr.forEach()

1. 作用域

作用域 scope: 变量和函数的可访问范围 => 作用域控制着变量和函数的可见性和生命周期。

1.1 全局作用域

    1. 直接写在script标签中的js代码
    1. 单独的.js文件

可见性:全局作用域中的代码,在任何地方都可以访问到 生命周期:伴随着页面的生命周期,关闭页面不能访问

javascript
// eg1. 任何地方都能访问全局作用域中变量
const num = 10 
function fn(){
    // 函数内部是可以访问的
    console.log(num)
}
fn()

// eg2. 函数内部,如果不适用let或者const声明的变量,会成为全局变量
function bar(){
  abc = 20
}
bar()

1.2 局部作用域(函数作用域)

局部作用域就是函数作用域 => 在函数内部声明的变量,只能在函数内部访问,在外部是无法访问的

javascript
function getNum(){
  // 内部的变量,外部无法访问
  const num = 10
}
getNum()
console.log(num)

1.3 块级作用域

{}包裹起来的叫代码块,使用let或者const声明的变量,在{}中会产生块级作用域

块级作用域 特点:

  1. 只有let / const 会产生块级作用域
  2. 块级作用域外部无法访问内部的变量
  3. 两个块级作用域的变量相互不影响
  4. if / for 是语句,不是函数,但是和let、const搭配,可以形成块级作用域
javascript
{
    let obj = {
        name:'ll',
        age:18
    }
    console.log(obj)
}
console.log(obj) // Error 

/* ==================== 两个块级作用域的变量相互不影响 ===================== */
for(let i = 0; i < 3; i++){
    console.log(i)
}

for(let i = 0; i < 3; i++){
    console.log(i)
}
/* ==================== 刚才的问题 ===================== */
if(false){
    let temp = 'hello world'
}
console.log(temp)

1.3.1 为什么新增块级作用域

    1. 内层变量可能会覆盖外层变量 ==> var
    1. 用来计数的i变量可能会泄漏为全局变量 ==> var
javascript
// eg1.
// var temp = new Date()
function fn(){
    console.log(temp) // 本来正常逻辑,打印日期对象
    if(false){
        var temp = 'hello world'
    }
}
// fn()
// 但是现在,var变量提升, ===>
var temp = new Date() // 全局
function fn(){ 
    var temp  // 局部
    console.log(temp) // 本来正常逻辑,打印日期对象  undefined
    if(false){
        temp = 'hello world'
    }
}
fn()

// eg2. 
for(var i = 0; i <= 3; i++){
    console.log(i) // 0 - 3 
}
console.log(i) // 4

// ==> 
var i;
// 当for循环全部执行完之后,i++,那么i变为4
for(i = 0; i <= 3; i++){
    console.log(i) // 0 - 3 
}
console.log(i) // 4

1.3.2 注意

  1. for 、 if 不是函数,是语句!!!
  2. var声明的变量会提升到当前作用域的顶层,赋值没有提升
  3. var声明的变量,如果不赋值,初始为undefined

1.4 var-let-const

var let const的区别?背下来

  1. let / const 会产生块级作用域
  2. let / const 不存在变量提升,var有
  3. let / const 不允许重复声明,var可以
  4. let / const 存在暂时性死区(temporal dead zone) TDZ ==> let / const 声明的变量,不能在声明之前使用, var可以
  5. 浏览器中,var声明的变量会挂载到window对象下, let / const 不会

**针对const **

  1. const 一旦声明,必须赋值
  2. const声明的变量不能改变值,基本数据类型不能改变值,引用数据类型不能改变地址。
javascript
// 3 => 不允许重复声明
// let p1 = 123
// let p1 = 123

// 4 => TDZ
if (true){
    tmp = 'abc'
    let tmp;
}

// ===> const 相关
// 1. const 一旦声明,必须赋值
let p1; var p2;

const p3 = '马上赋值'
// const p4 ; // Error 

// 2. const声明的变量不能改变值,基本数据类型不能改变值,引用数据类型不能改变地址。
// const p5 = '不能改变'
// p5 = '666'

const p6 = {
    name:'我就想改变',
    age:666
}
// 引用类型,这里赋值,赋的是地址
// p6 = {name:'11'} // Error 
p6.name = '改变了'
console.log(p6)

1.4.1 var加深印象

javascript
<button>click me</button>
<button>click me</button>
<button>click me</button>
<button>click me</button>
<button>click me</button>
<script>
    // 给五个按钮分别注册点击事件
    const btns = document.querySelectorAll('button')

    // var i;
    // for( i = 0; i < btns.length; i++){
    //     // for循环执行完之后,i变成5了,当我们点击按钮的时候,
		//   for循环已经执行完了,i已经变为了5
    //     btns[i].addEventListener('click', function(){
    //         console.log(i)
    //     })
    // }
    // btns[0].addEventListener('click', function(){
    //         console.log(i)
    //     })
    // btns[1].addEventListener('click', function(){
    //     console.log(i)
    // })
    // btns[i].addEventListener('click', function(){
    //     console.log(i)
    // })


    // eg2.
    // let + {}  ==> 块级作用域, for循环中的点击事件,内部访问了i变量,
    // for循环中的i,和点击事件的log i 相互捆绑在一起了
  	// 点击第0个按钮,访问i = 0这个变量,打印i = 0;
  	// 点击第1个按钮,访问i = 1这边变量,打印i = 1 
    for(let i = 0; i < btns.length; i++){
        btns[i].addEventListener('click', function(){
            console.log(i)
        })
    }

1.5 作用域链

函数是可以嵌套函数的,每个函数都有一个局部作用域,这样子就会形成作用域的嵌套 作用域的嵌套关系就形成了作用域链

  1. 作用域链的本质: 底层变量的查找机制
  2. 查找规则:就近原则
  1. 优先在当前作用域中查找变量
  2. 如果不存在,往上层作用域中查找变量,直到全局作用域
  3. 如果都没有,Error,变量没有定义过

2. 垃圾回收机制

Garbage Collection ==> 不就是GC吗? JS中内存的分配和回收都是自动的,内存在不使用的时候会被JS引擎/垃圾回收程序会自动回收

内存泄漏: 不再使用的内存,没有被即使释放掉

javascript
// 1.全局变量一般不会回收(关掉页面回收)
// 2.一般情况下,局部变量的值,不用了,会被自动回收

// eg1. 全局变量  ==> 关掉页面,会被回收
const i = 666 
const str = 'hello JS advance'

const obj = {
    name:'霖霖不要睡'
}

// eg2.
const fn = function(){
    // 这里的num为局部变量,函数调用完之后,就会被自动回收
    const num = 10
    console.log(num)
}
fn()

2.1 引用计数法 淘汰

image.png

javascript
let person = {
  age: 18,
  name: '练练老师'
}
let p = person
person = 1
p = null

// 步骤

2.1.1 引用计数法缺陷-循环引用-内存泄漏

image.png

javascript
function problem () {
    let oA = {}  // oA变量名存的是一个地址
    let oB = {}  // oB也是存了一个地址,地址指向堆内存中的哪个对象
    oA.c = oB  //  oB被oA引用
    oB.d = oA  //  oA又被oB引用
}
problem()

// function fn(){
//     const a = 10 // 局部变量,在函数调用完之后,会回收掉
// }
// fn()

// 调用完problem这个函数之后, oA,oB两个变量会被回收掉,
// 但是堆内存空间中间的两个对象相互引用,计数永远不为0.
// 这个对象会一直占用内存空间,造成内存泄漏

2.2 标记清除法

  1. 标记阶段: 标记内存为活动对象和非活动对象
  2. 清除阶段:回收非活动对象的内存,也就是销毁清除非活动对象
javascript
==> 造成问题:内存碎片化  ==》 想象教室里的座

==> 优化:重新排列座位(内存) ==> 标记整理算法

3. 闭包

闭包 = 内层函数 + 外层函数的变量 内部函数引用外部函数的变量的集合

javascript
// 闭包?
/* ==================== 面试题 ===================== */
// 闭包 = 内层函数 + 外层函数的变量
// 内部函数引用外部函数的变量的集合  
/* ==================== end ===================== */ 

// 条件
// 1. 首先要有内层函数(嵌套关系)
// 2. 内层函数使用了外层函数的变量

function outer(){
    // const a = 1 
    function inner(){
        console.log(a)
    }
    inner()
}
outer()

// Scope作用域:
// Closure闭包  Block块级作用域

3.1 闭包的作用

通过闭包,外部可以访问到函数内部的变量

javascript
// 正常情况下,外部作用域无法访问内部作用域中的变量
// function outer(){
//     let a = 10
// }
// outer()
// console.log(a)

// ==> 1. 闭包的作用:通过闭包,外部可以访问到函数内部的变量
function outer(){
    let a = 10 
    function fn(){
        console.log(a)
    }
    // fn()
    return fn
}
let fun = outer()
// 虽然看起来是在外部调用的fun,本质上,是调用的内层的fn
// let fun = function fn(){
//     console.log(a)
// }
// 我们外部调用fun,相当于执行上面注释的函数,函数内部log-a, 找的是outer里面最开始定义的a
// 因为一开始他们就形成了闭包,一种捆绑关系。
fun()

// ===> 2.闭包的简单写法
function outer(){
    let a = 10 
    return function (){
        console.log(a)
    }
}
const bar = outer()
bar()

3.2 闭包的应用/作用

实现数据的私有化,避免全局污染,外层函数也可以访问内存函数的变量

javascript
// let i = 0 // 全局变量
// function fn(){
//     i++
//     console.log(`调用了${i}次`)
// }
// fn()

// 换成闭包的形式来写这个函数

function count(){
    let i = 0 // 外部不能修改这个变量,但是可以访问
    return function(){
        i++
        console.log(`调用了${i}次`)
    }
}
const bar = count()
// const bar = function(){
//         i++
//         console.log(`调用了${i}次`)
//     }
bar()

// 闭包的应用:实现数据的私有化,避免全局污染,外层函数也可以访问内存函数的变量

3.3 闭包的问题

=> 内存泄漏

javascript
// i变量在count函数里面,是一个局部变量,当count()调用执行完之后,按理说它应该被回收掉
// 但是现在,我么们每次调用bar()的时候,一直都访问到了这个i变量。

// 标记清除法:从全局作用域触发,能访问到的变量/变量标记为活动对象,不回收

// 这个i变量本来应该被回收,现在一直存在内存里面,所以造成了内存泄漏

// ==> 闭包:会造成内存泄漏
function count(){
    let i = 0 // 局部变量
    return function(){
        i++
        console.log(`调用了${i}次`)
    }
}
const bar = count()
// const bar = function(){
//         i++
//         console.log(`调用了${i}次`)
//     }
bar()

// function fn(){
//     const num = 10 // 调用之后,不再使用,回收掉了
// }
// fn()

4. 变量提升-函数提升

4.1 变量提升

  1. 所有var声明的变量会在执行之前,提升到当前作用域的最前面
  2. var声明的变量,只提升声明,赋值不提升
  3. var声明的变量,如果不赋值,默认为undefined
javascript
// 1. 所有var声明的变量会在执行之前,提升到当前作用域的最前面
// 2. var声明的变量,只提升声明,赋值不提升
// 3. var声明的变量,如果不赋值,默认为undefined

// var b
// b = 6

console.log(`${num}把剑~`) // undefined把剑
var num = 10 

// eg2.
function fn(){
    var temp // var的变量提升
    console.log(temp)
    if(false){
         temp = 'hello js'
    }
}
fn()

4.2 函数提升

建议 : 先声明,后调用

javascript
// 1. function声明的函数,会提升到当前作用域的最前面 (在所有代码执行之前)
fn()

function fn(){
  console.log(1111)
}

// 2. 函数表达式情况,如果是var,只提升声明,不提升赋值
// foo()
const foo = function(){
  console.log(222)
}

// bar()
console.log(bar)
var bar = function(){
  console.log(333)
}

5. 参数

5.1 arguments对象

  1. 除箭头函数外,所有函数都内置了(默认的,自带的)一个arguments 对象,
  2. 它是一个伪数组,(有length;有索引,但是没有数组的方法)存储了函数传过来的所有实参
  3. arguments对象只存在于函数中
javascript

function getSum(){
    console.dir(arguments)
    let sum = 0
    for(let i = 0; i < arguments.length; i++){
        sum += arguments[i]
    }
    // arguments.forEach(el => sum += el); Error
    return sum
}

// getSum(1, 2)
console.log(getSum(1, 2, 3))
// console.log(getSum(2, 3, 4))

5.2 剩余参数rest

形式 (...变量名) 变量名是我们自己定义的

  1. 剩余参数是一个真数组,存了剩余的实参
  2. 只能放到参数的最后一位
javascript

// function getSum(...arr){
//     console.log(arr)
// }

// function getSum(a,  b, ...arr){
//     console.log(arr)
// }

// getSum(1, 2, 3, 5, 6)

// 求和函数,改为箭头函数的形式,
const getSum = (...args) => {
    let sum = 0 
    args.forEach(el => sum += el)
    return sum
}
console.log(getSum(1,2,3,4))

6. 扩展运算符

扩展运算符 spread 也是三个点... 作用:将一个数组转为用逗号隔开的参数列表

6.1. 求数组的最大/最小值

javascript

const arr = [1, 2, 3, 4, 5]
console.log(...arr) // 浏览器上没有显示逗号,假装有逗号


// 主要应用
// 1. 求一个数组中的最大值/最小值
const arr2 = [11, 2, 99, 8, 7]
Math.max(11, 2, 99, 8, 7) // ==> 它接收的是参数列表
console.log(Math.max(...arr2))

6.2 拷贝数组(浅拷贝)

image.png

javascript
// 2. 复制/拷贝数组 (浅拷贝)
// ==> 复制或者拷贝,应该是产生一个新的数组

// 2.1 赋值的情况 不是拷贝
const a1 = [1, 2]
const a2 = a1 // 这个叫赋值!!因为针对的是引用类型,赋的是地址,指向堆内存中同一个数组对象
a1[0] = 6
console.log(a2)

// 2.2 拷贝的情况
const a3 = [1, 2, 3]
const a4 = [...a3]
// console.log(a4)
a3[0] = 11
console.log(a4)

怎么区分扩展运算符还是rest剩余参数?

  1. 扩展运算符是展开数组或字符串,后面根数组或字符串 [...arr], [...'hello'], 常和数组[] 搭配 (拆散,展开)
  2. 剩余参数rest在函数的形参上使用,将不定数量的参数列表 表示为一个真数组 (凝聚)

6.3 合并数组

javascript
// 3. 合并数组
const a5 = ['c', 'd']
const a6 = ['e', 'f']
const resArr = a5.concat(a6)
console.log(resArr)
const a7 = [...a5, ...a6]
console.log(a7)

6.4 展开字符串

反转字符串

  1. const res = str.split('').reverse().join('')
  2. const res1 = [...str].reverse().join('')
javascript
// 4. 扩展运算符,还可以展开字符串
const str = 'hello world'
const s1 = [...str]
console.log(s1)

// ==> 反转数组
// arr.reverse() ==> 将数组中的元素反转  [1, 2, 3] ==> [3, 2, 1]
// ==> 将字符串转为数组
// str.split('')

// ==> 反转字符串
// dlrow olleh
// 方式1:
const res = str.split('').reverse().join('')
//  方式2:
const res2 = [...str].reverse().join('')
console.log(res)
console.log('----------')
console.log(res2)
PS. str.split('')
javascript
// str.split(' ')把字符串转为数组,以分割符分割
const str = 'The quick brown fox jumps over the lazy dog.';

const words = str.split(' ');
console.log(words)

const str2 ='2023-04-06'
const res = str2.split('-')
console.log(res)

6.5 将伪数组转为真数组

javascript
// 5. 扩展运算符,可以把伪数组转为真数组
// 1. [...likeArr] 
const divs = document.querySelectorAll('div')
console.log(divs)
const resDivs = [...divs]
resDivs.push(666)
console.log(resDivs)
resDivs[0].innerHTML = '666,真的可以转'

// 2. Array.from(likeArr)
const resReal = Array.from(divs)
console.log(resReal)
resReal.push(1211)
console.log(resReal)

/* ==================== 将伪数组转为真数组的方式 ===================== */
// 1. [...likeArr]
// 2. Array.from(likeArr)

7. 箭头函数

ES6新增 : function 写成 =>

7.1 基本用法

javascript
const fn = function(){
    console.log(111)
}
fn()

// 1. 基本写法,就是
const fn1 = () => {
    console.log(111)
}
fn1()

// 2. 只有一个形参的时候,可以省略小括号
// const fn2 = (x) => {
//     console.log(x)
// }
const fn2 = x => {
    console.log(x)
}
fn2(222)

// 3. 只有一行代码的时候,可以省略大括号(花括号{})
// const fn3 = x => {
//     console.log(x)
// }
const fn3 = x => console.log(x)
fn3(333)


// 4. 只有一行代码的时候,可以省略return
// const fn4 = (x) => {
//     return x + x 
// }
const fn4 = x => x + x 
console.log(fn4(6))

// 5. 箭头函数可以返回一个对象,但是对象需要用小括号包起来
// const fn5 = (name) => {
//     return {name:name, age:18}
// }
const fn5 = name => ({name:name, age:18})
console.log(fn5('练练'))

7.2 箭头函数的this

javascript
// 以前的this指向 : 谁调用这个函数,this就指向谁 

// 1. this指向在定义的时候不能确定,只有在调用执行的时候才能确定!!!
// 2. 箭头函数本身没有this,它的this是找上层作用域中的this,定义的时候已经确定了

var name = '霖霖'
const obj = {
    name:'刘翔',
    fn:function(){
        console.log(this.name)
    }
}

let fun = obj.fn
fun()   // ==> 
obj.fn()  // ==> this指向obj


// eg1.
const fn = () => {
    console.log(this)
}
fn()

// eg2. 作用域:全局 / 局部(局部)/ 块级
// 对象方法中,不建议使用箭头函数
const obj2 = {
    name:'刘翔',
    fn:() => {
        console.log(this.name)
    }
}
obj2.fn()

// eg3 事件绑定中, 也不建议使用箭头函数
const btn = document.querySelector('button')
// btn.addEventListener('click', function(){
//     console.log(this)
// })
btn.addEventListener('click', () => {
    console.log(this) // window
})

8. 解构赋值

ES6 允许按照一定模式(规则),从数组和对象中提取值,对变量进行赋值,这被称为解构

8.1 对象属性和方法的简写

image.png

javascript
// 1. 对象属性的简写
// 属性名和属性值相同的时候,可以只写属性名
const username = '建国'
const age = 18 
// const obj = {
//     username:username,
//     age:age
// }

const obj = {
    username,
    age
}

console.log(obj)

// 2. 对象方法的简写
// function可以省略~~
const obj2 = {
    username,
    age,
    sayHi: function(){
        console.log('Nice to meet U :)')
    }
}
const obj3 = {
    username,
    age,
    sayHi(){
        console.log('Nice to meet U :)')
        return 666
    }
}
console.log(obj3.sayHi())

8.2 数组的解构

注意:数组的解构有顺序!!!

javascript
// ES5 为变量赋值
// let a = 1;
// let b = 2;
// let c = 3;

// 1. ES6 解构赋值:
// ES6 允许按照一定模式(规则),从数组和对象中提取值,对变量进行赋值,这被称为解构
let [a, b, c] = [1, 2, 3]
// 从等号的右侧,按照对应的位置,提取值,给左边的变量 
// 数组的解构 ==> 有顺序

const arr = [100, 60, 80]
// const max = arr[0]
// const min = arr[1]
// const avg = arr[2]
// ==> 
// const [max, min, avg] = [100, 60, 80]
const [max, min, avg] = arr

// 2. 如果左侧不加关键字,相当于是var声明的变量,不建议这么使用
;[af, ag] = [66, 77]
console.log(window)

// 3. 数组的解构常见的应用:交换两个变量
let d = 11
let f = 22

let tmp = 0
// tmp = d 
// d = f 
// f = tmp
;[d, f] = [f, d]
console.log(d, f)

// 4. 什么时候要加分号?
// ;()
// ;[]

8.2.1 数组解构的一些细节


javascript
// 1. 左边的变量多 右边的值(数组元素)少
const [a, b, c, d] = [1, 2, 3]
console.log(a, b, c, d) // undefined

// 2. 左边的变量少,右边的值多
const [e, f] = [1, 2, 3]
console.log(e, f)

// 3. 左边的变量少,右边值多,怎么接收多的一些参数?可以用剩余参数rest(真数组)
const [g, h, ...i] = [1, 2, 3, 4, 5]
console.log(g, h, i)

// 4. 数组的解构,一定要注意顺序,按照对应的位置赋值
const [j, k, ,l] = [1, 2, 3, 4]
console.log(j, k, l)

// 5. 数组的解构,还可以设置默认值 
const [m = 0, n = 0] = [66, 77]
console.log(m, n)
javascript
// 5. 数组的解构,还可以设置默认值 
const [m = 0, n = 0] = [66, 77]
console.log(m, n)

// Q: 默认值什么时候生效?
const [o = 0, p = 0] = [88]
// eg1. 当右侧的数组对应的值不存在的时候,默认值生效
console.log(o, p)

// eg2. 右侧的值严格===等于undefined的时候,默认值生效
const [aa = 0, bb = 1] = ['undefined', undefined]
console.log(aa, bb) // undefined 1 

// 6. 如果右侧不是数组,那么会报错
const [f1] = 1
const [f2] = null 
const [f3] = undefined
// ...
console.log(f1)
eg. 数组解构练习
javascript
const pc = ['海尔', '联想', '小米', '方正']

function getValue(){
    return [100, 60]
}

8.2.2 多维数组

多维数组: 数组嵌套数组 数组的解构是一种模式(规则)匹配,左侧和右侧的模式一样

javascript
// 多维数组: 数组嵌套数组
const arr = [1, 2, [3, 4]] // 二维数组
console.log(arr[0])
console.log(arr[1])
console.log(arr[2])  // [3, 4]

console.log(arr[2][0])
console.log(arr[2][1])

const arr1 = [1, 2, [3, [4]]]  // 三维数组

console.log(arr1[2])
console.log(arr1[2][1])
console.log(arr1[2][1][0])

// 多维数组的解构
const [a, b, c] = arr 
// const [a, b, c] = [1, 2, [3, 4]] 
console.log(a, b, c)

// d, e, f, g => 1, 2, 3, 4
// 数组的解构是一种模式(规则)匹配,左侧和右侧的模式一样
const [d, e, [f, g]] = [1, 2, [3, 4]]
console.log(d, e, f, g)

8.2.3 数组解构小结

javascript
// 本质上,解构的语法属于模式匹配,只要等号两侧的模式相同(长得一样),
// 就可以把右边的值,赋值给左边的变量

let [a, [b, [c]]] = [1, [2, [3]]]
console.log(a, b, c)

let [, , d] = ['1', '2', '3']
console.log(d)

let [head, ...tail] = [1, 2, 3, 4]
console.log(tail)

console.log('------------')
// 难度:
let [aa, bb, ...cc] = ['a']
console.log(aa, bb, cc) // 'a' undefined []

// 如果解构不成功,变量的值是 undefined
let [foo] = [] 
console.log(foo)

let [f1, f2] = [1]
console.log(f1, f2)

8.3 对象解构

javascript
// 对象的解构
// 右侧的属性值,赋值给左侧的变量
const obj = {
    name:'罗广',
    age:18
}
// 1. 对象的解构,是按着属性名解构, 可以交换位置
// const { age, name } = obj
// console.log(name, age)

// 2. 要求变量名必须和属性名一致才可以解构出对象中的属性值
const {username, age} = obj 
// console.log(username, age) // undefined 

// 3. 解构某一个属性 
// const { age } = obj
// console.log(age)

const { name } = {name:'刘翔', age:99, gender:'female'}
console.log(name)

8.3.1 对象解构重命名

javascript
// 对象解构的变量名,可以重新命名   旧变量名: 新变量名
const obj = {
    name: '郑忠文',
    age:88
}
const {name:user_name, age} = obj 
console.log(name, age)
console.log(user_name, age)

const obj2 = {first:'hello', last:'world'}
// Q : 解构出first 和 last  ==> f , l 
const {first:f, last:l} = obj2
console.log(f, l)

// 对象解构的内部机制
// 1. 先找到同名的属性,然后再赋值给对应的变量
// 2. 如果有冒号重命名,被赋值的是冒号后面的那个新变量名
// ==> 注意:对象的解构,没有顺序

8.3.2 数组对象的解构-eg.练习

javascript
const pig = [{
    name:'佩奇',
    age:6
}]

// const [{uname, age}] = pig
// console.log(uname, age)

// 练习
// 1. 
const pig = {name:'佩奇', age: 6}
const {name, age} = pig
console.log(name, age)

// 2. 
const {name:uname} = pig
console.log(uname)

// 3. 
const goods = [{goodsName:'小米', price:1999}]
const [{goodsName, price}] = goods

console.log(goodsName, price)

8.3.4 多级对象的解构 ==> 重点注意

javascript

// 多级对象  ==> 对象中还有对象,对象的属性值还是对象
const pig = {
    name: '佩奇',
    family: {
        mother: '猪妈妈',
        father: '猪爸爸',
        sister: '乔治'
    },
    age: 6
}

// 多级对象的解构
// const {name, family} = pig
// console.log(name, family)
// Q: 想要把family对象中的属性再解构出来 
const {name, family:{mother, father, sister}, age} = pig
console.log(name, mother, father, sister, age)

// 每当有一个冒号在解构赋值中出现
// 就意味着冒号左边的变量需要检查位置(找对象中这个属性名),冒号右边是赋值的目标

// 这里的family, 相当于是找到了pig里面的 {mother: '猪妈妈',father: '猪爸爸',sister: '乔治'}

8.3.5 函数参数的解构

javascript
// 1. 在函数内部解构参数
const pos = {x:10, y:20}
function move(pos){
    console.log(pos)
    let {x, y} = pos
    // let x = pos.x 
    // let y = pos.y
    console.log(x, y)
}
move(pos)

// 2. 在形参的位置,可以直接解构  
function move({x, y}){
    // let {x, y} = pos 
    // let x = pos.x 
    // let y = pos.y
    x = 666
    console.log(x, y)
}
move(pos)

// 3. 在形参的位置,解构,并且重命名
function move({x:aa, y}){
    // let {x, y} = pos 
    // let x = pos.x 
    // let y = pos.y
    console.log(aa, y)
}
move(pos)

// 4. 如果函数的参数是数组
// function add(arr){
//     let [x, y] = arr
//     // console.log()
//     console.log(x + y)
// }

function add([x, y]){
    // let [x, y] = arr
    // console.log()
    console.log(x + y)
}

add([11, 22])

8.3.6 多级对象解构案例

javascript
// 这是后台传递过来的数据
const msg = {
    "code": 200,
    "msg": "获取新闻列表成功",
    "data": [
        {
        "id": 1,
        "title": "5G商用自己,三大运用商收入下降",
        "count": 58
        },
        {
        "id": 2,
        "title": "国际媒体头条速览",
        "count": 56
        },
        {
        "id": 3,
        "title": "乌克兰和俄罗斯持续冲突",
        "count": 1669
        },
    ]
}

// 需求1: 请将以上msg对象  采用对象解构的方式 只选出  data 数据,后面使用渲染页面
// ==> 解构除msg中的data数组

// 需求2: 上面msg是后台传递过来的数据,我们需要把data选出当做参数传递给 函数
// ==> 在函数的形参上直接解构除data数据

function render() {

}
render(msg)

// 需求3, 为了防止msg里面的data名字混淆,要求渲染函数里面的数据名改为 myData
// ===> 解构data数据,并且重命名
function render() {

}
render(msg)

8.3.7 渲染商品 ==> map+join必须掌握

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>商品渲染</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    .list {
      width: 990px;
      margin: 0 auto;
      display: flex;
      flex-wrap: wrap;
      padding-top: 100px;
    }

    .item {
      width: 240px;
      margin-left: 10px;
      padding: 20px 30px;
      transition: all .5s;
      margin-bottom: 20px;
    }

    .item:nth-child(4n) {
      margin-left: 0;
    }

    .item:hover {
      box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);
      transform: translate3d(0, -4px, 0);
      cursor: pointer;
    }

    .item img {
      width: 100%;
    }

    .item .name {
      font-size: 18px;
      margin-bottom: 10px;
      color: #666;
    }

    .item .price {
      font-size: 22px;
      color: firebrick;
    }

    .item .price::before {
      content: "¥";
      font-size: 14px;
    }
  </style>
</head>

<body>
  <div class="list">
    <!-- 
      <div class="item">
      <img src="" alt="">
      <p class="name"></p>
      <p class="price"></p>
      </div> 
    -->
  </div>
  <script>
    const goodsList = [
      {
        id: '4001172',
        name: '称心如意手摇咖啡磨豆机咖啡豆研磨机',
        price: '289.00',
        picture: 'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg',
      },
      {
        id: '4001594',
        name: '日式黑陶功夫茶组双侧把茶具礼盒装',
        price: '288.00',
        picture: 'https://yanxuan-item.nosdn.127.net/3346b7b92f9563c7a7e24c7ead883f18.jpg',
      },
      {
        id: '4001009',
        name: '竹制干泡茶盘正方形沥水茶台品茶盘',
        price: '109.00',
        picture: 'https://yanxuan-item.nosdn.127.net/2d942d6bc94f1e230763e1a5a3b379e1.png',
      },
      {
        id: '4001874',
        name: '古法温酒汝瓷酒具套装白酒杯莲花温酒器',
        price: '488.00',
        picture: 'https://yanxuan-item.nosdn.127.net/44e51622800e4fceb6bee8e616da85fd.png',
      },
      {
        id: '4001649',
        name: '大师监制龙泉青瓷茶叶罐',
        price: '139.00',
        picture: 'https://yanxuan-item.nosdn.127.net/4356c9fc150753775fe56b465314f1eb.png',
      },
      {
        id: '3997185',
        name: '与众不同的口感汝瓷白酒杯套组1壶4杯',
        price: '108.00',
        picture: 'https://yanxuan-item.nosdn.127.net/8e21c794dfd3a4e8573273ddae50bce2.jpg',
      },
      {
        id: '3997403',
        name: '手工吹制更厚实白酒杯壶套装6壶6杯',
        price: '99.00',
        picture: 'https://yanxuan-item.nosdn.127.net/af2371a65f60bce152a61fc22745ff3f.jpg',
      },
      {
        id: '3998274',
        name: '德国百年工艺高端水晶玻璃红酒杯2支装',
        price: '139.00',
        picture: 'https://yanxuan-item.nosdn.127.net/8896b897b3ec6639bbd1134d66b9715c.jpg',
      },
    ]
  </script>
</body>

</html>
javascript
// 1. 字符串拼接的方式渲染
let str = ''
// 2. 遍历数据
goodsList.forEach(function({picture, name, price}){
    // console.log(el)
    // const {picture, name, price} = el
    str+= `
        <div class="item">
            <img src="${picture}" alt="">
            <p class="name">${name}</p>
            <p class="price">${price}</p>
        </div> 
    `
})
// 3. 生成的str字符串,给list盒子
document.querySelector('.list').innerHTML = str

// ===> map的方法
javascript
const list = document.querySelector('.list')
// 形参上解构,箭头函数 小括号别忘记了({picture, name, price}) => {}
const resArr = goodsList.map(({picture, name, price}) => {
    // map 有返回值,里面要写return
    return `
        <div class="item">
            <img src="${picture}" alt="">
            <p class="name">${name}</p>
            <p class="price">${price}</p>
        </div> 
    `  
})
// 数组是不能放到html中的,要转为字符串
list.innerHTML = resArr.join('')

8.4 arr.filter() 过滤筛选

arr.filter() : 创建一个新数组,新数组的元素是通过筛选后的元素(满足筛选的条件)

javascript
// array.filter()      filter筛选,过滤的意思
// 作用:创建一个新的数组,新数组的元素是通过筛选后的元素 (满足筛选的条件)

// 参数
// array.filter 接收一个函数, 函数接收两个参数
// ==>  第一个参数    正在处理的元素
// ==>  第二个参数    index 索引号  (可选)

// 返回值: 由满足条件的元素,组成的新数组

// 注意:这个函数里面一般要写return, 看是否满足条件 返回 true / false
// ==> true 表示满足筛选条件,把这个元素放到新数组中
javascript
const arr = [10, 20, 30, 40]
// Q: 从上面的数组中筛选出 >= 20 的元素,组成一个新的数组

const resArr = arr.filter(function(el){
    return el >= 20
})
console.log(resArr)

const resArr2 = arr.filter(el => el >= 20)
console.log(resArr2)

// filter和map的区别? 
// map 映射,是把原数组中每个元素经过一些处理,得到一个和原数组长度一样的新数组
// filter 过滤,筛选,得到一个满足条件的新数组,元素个数可能减少

// 相同点:都有return,都有返回值,都不改变原数组

9. 综合案例-filter筛选

javascript
// 1. 封装一个render函数
const list = document.querySelector('.list')
// 传参的方式 ==> 改装render函数 
const render = (arr) => {
  const resArr = arr.map(({name, price, picture}) => {
    return `
    <div class="item">
      <img src="${picture}" alt="">
      <p class="name">${name}</p>
      <p class="price">${price}</p>
    </div>
    `
  })

  list.innerHTML = resArr.join('')
}
// 一进入页面,渲染整个数据,传完整的数组
render(goodsList)

// 之后点击按钮,筛选数据,重新调用render函数?
javascript
// 之后点击按钮,筛选数据,重新调用render函数?
const filter = document.querySelector('.filter')
filter.addEventListener('click', e => {
  // e.target.tagName
  // e.target.dataset.index
  // 想在这里解构一下对象 e.target
  const {tagName, dataset:{ index }} = e.target
  // e.target = {
  //   tagName:'A',
  //   dataset:{
  //     index: 1
  //   }
  // }
  console.log(tagName, index)
  if(tagName === 'A'){


  } // end if 
})
javascript
const filter = document.querySelector('.filter')
filter.addEventListener('click', e => {
  // e.target.tagName
  // e.target.dataset.index
  const {tagName, dataset:{ index }} = e.target
  if(tagName === 'A'){
    // 判断点击的哪个A标签,筛选数组,重新渲染数据
    let arr = []
    if(index === '1'){
      arr = goodsList.filter(el =>  el.price > 0 && el.price <= 100)
    } else if (index === '2'){
      arr = goodsList.filter(({price}) =>  price > 100 && price <= 300)
    } else if (index === '3'){
      arr = goodsList.filter(({price}) =>  price > 300)
    } else {
      arr = goodsList
    }
    // 如果代码里面很多判断if else if 
    // ===> switch 
    render(arr)
  } // end if 
})
javascript
if (tagName === 'A'){
  let arr = []
  switch(index) {
    // 注意 switch 的case 这里是全等,还要注意,写break
    case '1':
      arr = goodsList.filter(el =>  el.price > 0 && el.price <= 100)
      break
    case '2':
      arr = goodsList.filter(({price}) =>  price > 100 && price <= 300)
      break
    case '3':
      arr = goodsList.filter(({price}) =>  price > 300)
      break
    // 当前面的都没有匹配到的时候,默认值
    default:
      arr = goodsList
  }
  render(arr)
}