00 原型5条规则
- 所有的引用类型(数组,对象,函数),都具有对象的特性,可以自由扩展属性
- 所有的对象,都有一个__proto__的属性(隐式原型),属性值是一个普通的对象
- 所有的函数,都有一个prototype属性(显示原型),属性值也是一个普通对象
- 所有对象的隐式原型(proto), 指向它的构造函数的显示原型(prototype)
- 当试图得到某个对象的属性时,如果在这个对象本身没有这个属性,那么会去它的__proto__中寻找
上述的所有函数,不包含箭头函数 () => {}, 箭头函数没有prototype
原型五条规则 重要!!! 记住,背!!!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 全局作用域
- 直接写在script标签中的js代码
- 单独的.js文件
可见性:全局作用域中的代码,在任何地方都可以访问到 生命周期:伴随着页面的生命周期,关闭页面不能访问
// eg1. 任何地方都能访问全局作用域中变量
const num = 10
function fn(){
// 函数内部是可以访问的
console.log(num)
}
fn()
// eg2. 函数内部,如果不适用let或者const声明的变量,会成为全局变量
function bar(){
abc = 20
}
bar()
1.2 局部作用域(函数作用域)
局部作用域就是函数作用域 => 在函数内部声明的变量,只能在函数内部访问,在外部是无法访问的
function getNum(){
// 内部的变量,外部无法访问
const num = 10
}
getNum()
console.log(num)
1.3 块级作用域
{}包裹起来的叫代码块,使用let或者const声明的变量,在{}中会产生块级作用域
块级作用域 特点:
- 只有let / const 会产生块级作用域
- 块级作用域外部无法访问内部的变量
- 两个块级作用域的变量相互不影响
- if / for 是语句,不是函数,但是和let、const搭配,可以形成块级作用域
{
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 为什么新增块级作用域
- 内层变量可能会覆盖外层变量 ==> var
- 用来计数的i变量可能会泄漏为全局变量 ==> var
// 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 注意
- for 、 if 不是函数,是语句!!!
- var声明的变量会提升到当前作用域的顶层,赋值没有提升
- var声明的变量,如果不赋值,初始为undefined
1.4 var-let-const
var let const的区别?背下来
- let / const 会产生块级作用域
- let / const 不存在变量提升,var有
- let / const 不允许重复声明,var可以
- let / const 存在暂时性死区(temporal dead zone) TDZ ==> let / const 声明的变量,不能在声明之前使用, var可以
- 浏览器中,var声明的变量会挂载到window对象下, let / const 不会
**针对const **
- const 一旦声明,必须赋值
- const声明的变量不能改变值,基本数据类型不能改变值,引用数据类型不能改变地址。
// 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加深印象
<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 作用域链
函数是可以嵌套函数的,每个函数都有一个局部作用域,这样子就会形成作用域的嵌套 作用域的嵌套关系就形成了作用域链
- 作用域链的本质: 底层变量的查找机制
- 查找规则:就近原则
- 优先在当前作用域中查找变量
- 如果不存在,往上层作用域中查找变量,直到全局作用域
- 如果都没有,Error,变量没有定义过
2. 垃圾回收机制
Garbage Collection ==> 不就是GC吗? JS中内存的分配和回收都是自动的,内存在不使用的时候会被JS引擎/垃圾回收程序会自动回收
内存泄漏: 不再使用的内存,没有被即使释放掉
// 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 引用计数法 淘汰
let person = {
age: 18,
name: '练练老师'
}
let p = person
person = 1
p = null
// 步骤
2.1.1 引用计数法缺陷-循环引用-内存泄漏
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 标记清除法
- 标记阶段: 标记内存为活动对象和非活动对象
- 清除阶段:回收非活动对象的内存,也就是销毁清除非活动对象
==> 造成问题:内存碎片化 ==》 想象教室里的座
==> 优化:重新排列座位(内存) ==> 标记整理算法
3. 闭包
闭包 = 内层函数 + 外层函数的变量 内部函数引用外部函数的变量的集合
// 闭包?
/* ==================== 面试题 ===================== */
// 闭包 = 内层函数 + 外层函数的变量
// 内部函数引用外部函数的变量的集合
/* ==================== end ===================== */
// 条件
// 1. 首先要有内层函数(嵌套关系)
// 2. 内层函数使用了外层函数的变量
function outer(){
// const a = 1
function inner(){
console.log(a)
}
inner()
}
outer()
// Scope作用域:
// Closure闭包 Block块级作用域
3.1 闭包的作用
通过闭包,外部可以访问到函数内部的变量
// 正常情况下,外部作用域无法访问内部作用域中的变量
// 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 闭包的应用/作用
实现数据的私有化,避免全局污染,外层函数也可以访问内存函数的变量
// 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 闭包的问题
=> 内存泄漏
// 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 变量提升
- 所有var声明的变量会在执行之前,提升到当前作用域的最前面
- var声明的变量,只提升声明,赋值不提升
- var声明的变量,如果不赋值,默认为undefined
// 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 函数提升
建议 : 先声明,后调用
// 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对象
- 除箭头函数外,所有函数都内置了(默认的,自带的)一个arguments 对象,
- 它是一个伪数组,(有length;有索引,但是没有数组的方法)存储了函数传过来的所有实参
- arguments对象只存在于函数中
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
形式 (...变量名) 变量名是我们自己定义的
- 剩余参数是一个真数组,存了剩余的实参
- 只能放到参数的最后一位
// 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. 求数组的最大/最小值
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 拷贝数组(浅拷贝)
// 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剩余参数?
- 扩展运算符是展开数组或字符串,后面根数组或字符串 [...arr], [...'hello'], 常和数组[] 搭配 (拆散,展开)
- 剩余参数rest在函数的形参上使用,将不定数量的参数列表 表示为一个真数组 (凝聚)
6.3 合并数组
// 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 展开字符串
反转字符串
- const res = str.split('').reverse().join('')
- const res1 = [...str].reverse().join('')
// 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('')
// 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 将伪数组转为真数组
// 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 基本用法
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
// 以前的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 对象属性和方法的简写
// 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 数组的解构
注意:数组的解构有顺序!!!
// 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 数组解构的一些细节
// 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)
// 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. 数组解构练习
const pc = ['海尔', '联想', '小米', '方正']
function getValue(){
return [100, 60]
}
8.2.2 多维数组
多维数组: 数组嵌套数组 数组的解构是一种模式(规则)匹配,左侧和右侧的模式一样
// 多维数组: 数组嵌套数组
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 数组解构小结
// 本质上,解构的语法属于模式匹配,只要等号两侧的模式相同(长得一样),
// 就可以把右边的值,赋值给左边的变量
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 对象解构
// 对象的解构
// 右侧的属性值,赋值给左侧的变量
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 对象解构重命名
// 对象解构的变量名,可以重新命名 旧变量名: 新变量名
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.练习
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 多级对象的解构 ==> 重点注意
// 多级对象 ==> 对象中还有对象,对象的属性值还是对象
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 函数参数的解构
// 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 多级对象解构案例
// 这是后台传递过来的数据
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必须掌握
<!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>
// 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的方法
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() : 创建一个新数组,新数组的元素是通过筛选后的元素(满足筛选的条件)
// array.filter() filter筛选,过滤的意思
// 作用:创建一个新的数组,新数组的元素是通过筛选后的元素 (满足筛选的条件)
// 参数
// array.filter 接收一个函数, 函数接收两个参数
// ==> 第一个参数 正在处理的元素
// ==> 第二个参数 index 索引号 (可选)
// 返回值: 由满足条件的元素,组成的新数组
// 注意:这个函数里面一般要写return, 看是否满足条件 返回 true / false
// ==> true 表示满足筛选条件,把这个元素放到新数组中
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筛选
// 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函数?
// 之后点击按钮,筛选数据,重新调用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
})
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
})
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)
}