目录
三、JS进阶
1.作用域
目标:了解作用域对程序执行的影响及作用域链的查找机制,使用闭包函数创建隔离作用域避免全局变量污染。
作用域(scope)规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问,
作用域分为:
- 局部作用域
- 全局作用域
1.1局部作用域
局部作用域分为函数作用域和块作用域。
函数作用域:
- 在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
<script>
function getSum() {
//函数内部是函数作用域 属于局部变量
const num = 10
}
console.log(num) //此处报错 函数外部不能使用局部作用域变量
</script>
总结:
1.函数内部声明的变量,在函数外部无法被访问
2.函数的参数也是函数内部的局部变量
3.不同函数内部声明的变量无法互相访问
4.函数执行完毕后,函数内部的变量实际被清空了
块作用域:
在JavaScript中使用{ }包裹的代码称为代码块,代码块内部声明的变量外部将【有可能】无法被访问。
<script>
for (let i = 0; i <= 3; i++) {
//i只能在该代码中被访问
console.log(i)//正常
}
//超出了t的作用域
console.log(i)
</script>
1.let声明的变量会产生块作用域,var不会产生块作用域
2.const声明的常量也会产生块作用域
3.不同代码块之间的变量无法互相访问
4.推荐使用let或const
1.2全局作用域
<script>标签和.js文件的【最外层】就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问。
全局作用域中声明的变量,任何其它作用域都可以被访问
<script>
//全局作用域
//全局作用域下声明了num变量
const num = 10
function fn() {
//函数内部可以使用全局作用域的变量
console.log(num)
}
//此处全局作用域
</script>
注意:
1.为window对象动态添加的属性默认也是全局的,不推荐!
2.函数中未使用任何关键字声明的变量为全局变量,不推荐!!!
3.尽可能少的声明全局变量,防止全局变量被污染
1.3作用域链
作用域链本质上是底层的变量查找机制。
- 在函数被执行时,会优先查找当前函数作用域中查找变量
- 如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
总结:
1.嵌套关系的作用域串联起来形成了作用域链
2.相同作用域链中按着从小到大的规则查找变量
3.子作用域能够访问父作用域,父级作用域无法访问子级作用域
1.4JS垃圾回收机制
垃圾回收机制(Garbage Collection)简称GC
JS中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收
<script>
//为变量分配内存
const age = 18
//为对象分配内存
const obj = {
age: 19
}
//为函数分配内存
function fn() {
const age = 18
console.log(age)
}
</script>
内存的生命周期
JS环境中分配的内存,一般有如下生命周期:
1.内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存
2.内存使用:即读写内存,也就是使用变量、函数等
3.内存回收:使用完毕,由垃圾回收器自动回收不再使用的内存
说明:
全局变量一般不会回收(关闭页面回收)
一般情况下局部变量的值,不用了,会被自动回收掉
内存泄漏:程序中分配的内存由于某种原因程序未释放或无法释放叫做内存泄漏
1.5闭包
目标:能说出什么是闭包,闭包的作用以及注意事项
概念:一个函数对周围状态的引用捆绑在一起,内层函数中访问到其外层函数的作用域
简单理解:闭包=内层函数+外层函数的变量
先看个简单的代码:
<script>
function outer() {
const a = 1
function f() {
console.log(a)
}
f()
}
outer()
</script>
闭包作用:封闭数据,提供数据,外部也可以访问函数内部的变量
闭包的基本格式:
<script>
//常见的闭包形式 外部可以访问使用 函数内部的变量
function outer() {
const a = 1
function fn() {
console.log(a)
}
return fn
}
// outer() === fn === function fn () {}
//const fun = function fn () {}
const fun = outer()
fun() //调用函数
</script>
闭包应用:实现数据的私有
比如,我们要做个统计函数调用次数,函数调用一次,就++
但是,这个count是个全局变量,很容易被修改
1.6变量提升
目标:了解什么是变量提升
变量提升是JavaScript中比较“奇怪”的现象,它允许在变量声明之前即被访问(仅存在于var声明变量)
<script>
//1.把所有var声明的变量提升到当前作用域的最前面
//2.只提升声明,不提升赋值
console.log(num + '件')
var num = 10
//等于
var num
console.log(num+'件')
num = 10
</script>
注意:
1.变量在未声明即被访问时会报语法错误
2.变量在var声明之前即被访问,变量的值为undefined
3.let/const声明的变量不存在变量提升
4.变量提升出现在相同作用域当中
5.实际开发中推荐先声明再访问变量
说明:
JS初学者经常花很多时间才能习惯变量提升,还经常出现一些意想不到的bug,正因为如此,ES6引入了块级作用域,
用let或者consti声明变量,让代码写法更加规范和人性化。
2.函数进阶
2.1函数提升
目标:能说出函数提升的过程
函数提升与变量提升比较类似,是指函数在声明之前即可被调用。
<script>
//1.会把所有函数声明提升到当前作用域的最前面
//2.只提升函数声明,不提升函数调用
fn()
function fn() {
console.log('函数提升')
}//不报错
fun() //fun不是函数,是变量。变量只提升声明,不提升赋值
var fun = function(){
console.log('函数表达式')
}//报错
//函数表达式 必须先声明和赋值,后调用否则报错
</script>
总结:
1.函数提升能够使函数的声明调用更灵活
2.函数表达式不存在提升的现象
3.函数提升出现在相同作用域当中
2.2函数参数
动态参数:
arguments是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参
当不确定传递多少个实参的时候,使用arguments
<script>
function getSum() {
//arguments 动态参数 只存在于 函数里面
//是伪数组
let sum = 0
for (let i = 0; i < arguments.length; i++) {
sum += arguments[i]
}
console.log(sum) //20
}
getSum(2, 3, 4, 5, 6)
</script>
总结:
1.arguments是一个伪数组,只存在于函数中
2.arguments的作用是动态获取函数的实参
3.可以通过fo循环依次得到传递过来的实参
剩余参数:
目标:能够使用剩余参数
剩余参数允许我们将一个不定数量的参数表示为一个数组
<script>
function getSum(...other) {
//other得到[1,2,3]
console.log(other)
}
getSum(1,2,3)
</script>
...是语法符号,置于最末函数形参之前,用于获取多余的实参
借助...获取的剩余实参,是个真数组
提倡使用剩余参数
<script>
function getSum(a,b,...other) {
console.log(other)
}
getSum(1,2)// 1 对应 a ,2 对应 b
getSum(1,2,3,4,5,6)// 4,5,6对应other
</script>
展开运算符:
目标:能够使用展开运算符并说出常用的使用场景
展开运算符(...),将一个数组进行展开
<script>
const arr = [1,2,3]
//展开运算符 可以展开数组
console.log(...arr)
</script>
说明:
1.不会修改原数组
典型运用场景:求数组最大值(最小值),合并数组等
<script>
const arr = [1,2,3]
//求数组中的最大值
console.log(Math.max(...arr))
console.log(Math.min(...arr))
</script>
<script>
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
//合并数组
//...arr1 === 1,2,3
const arr = [...arr1,...arr2]
</script>
2.3箭头函数
目标:能够熟悉箭头函数不同写法
目的:引入箭头函数的目的是更简短的函数写法并且不绑定this,箭头函数的语法比函数表达式更简洁
使用场景:箭头函数更适用于那些本来需要匿名函数的地方
基本语法:
<script>
const fn = function () {
console.log(123)
}
//箭头函数
const fn = () => {
console.log(123)
}
fn()
//1.传参
const fn = (x) => {
console.log(x)
}
fn(1)
//2.只有一个形参的时候可以省略
const fn = x => {
console.log(x)
}
fn(1)
//3.只有一行代码的时候可以省略大括号
const fn = x => console.log(x)
fn(1)
//4.如果在一行里面可以省略return
// const fn = x => {
// return x + x
// }
const fn = x => x + x
console.log(fn(1)) //2
//5.箭头函数可以直接返回一个对象
//因为箭头函数的大括号与对象的大括号冲突了,所以使用小括号
//name是对象的属性名,uname是传入的参数
cosnt fn = (uname) => ({ name: uname })
fn('刘德华')
</script>
箭头函数参数:
1.普通函数有arguments动态参数
2.箭头函数没有arguments动态参数,但是有剩余参数..args
<script>
//利用箭头函数求和
const getSum = (...arr) => {
let sum = 0
for (i = 0; i < arr.length; i++)
sum += arr[i]
}
const result = getSum(2,3,4)
console.log(result)
</script>
箭头函数this:
- 在箭头函数出现之前,每一个新函数根据它是被如何调用的来定义这个函数的this值,非常令人讨厌。
- 箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this。
- 在开发中【使用箭头函数前需要考虑函数中this的值】,事件回调函数使用箭头函数时,this为全局的window,因此DOM事件回调函数为了简便,还是不太推荐使用箭头函数
<script>
// 箭头函数的this 是上一层作用域的this指向
const fn = () => {
console.log(this)//windows
}
fn()
//对象方法箭头函数this
const obj = {
uname: 'pink',
sayHi: () => {
console.log(this)//windows
}
}
obj.sayHi()
const obj = {
uname: 'pink',
sayHi: function () {
console.log(this) //obj
const count = () => {
console.log(this) //obj
}
count()
}
}
obj.sayHi()
</script>
3.解构赋值
3.1数组解构
数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法。
基本语法:
1.赋值运算符=左侧的 [ ] 用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量
2.变量的顺序对应数组单元值的位置依次进行赋值操作
<script>
const arr = [100, 60, 80]
//数组解构 赋值
//const [max,min,avg]=arr
const [max, min, avg] = arr
//const max = arr[0]
//const min = arr[1]
//const avg = arr[2]
</script>
独立完成数组解构赋值
需求①:有个数组:const pc=['海尔','联想','小米','方正]
结构为变量:hr lx mi fz
需求②:请将最大值和最小值函数返回值解构max和min两个变量
变量多 单元值少 的情况:
<script>
const [a, b, c, d] = ['苹果', '小米', '华为']
console.log(a)//苹果
console.log(b)//小米
console.log(c)//华为
console.log(d)//undefined
</script>
变量少 单元值多 的情况:
<script>
const [a, b] = ['苹果', '小米', '华为']
console.log(a)//苹果
console.log(b)//小米
</script>
剩余参数 变量少 单元值多 的情况:
<script>
const [a, b,...c] = ['1', '2', '3','4']
console.log(a)//1
console.log(b)//2
console.log(c)//[3,4]
</script>
防止有undefined传递单元值的情况,可以设置默认值:
<script>
const [a='1',b='2'] = ['3']
console.log(a)//3
console.log(b)//2
</script>
按需导入赋值:
<script>
const [a,b, ,d] = ['1','2','3','4']
console.log(a)//1
console.log(b)//2
console.log(d)//4
</script>
支持多维数组的解构:
<script>
const arr = [1, 2, [3, 4]]
const [a, b, c] = [1, 2, [3, 4]]
console.log(a)//1
console.log(b)//2
console.log(c)//[3,4]
//多维数组解构
const [a, b, [c, d]] = [1, 2, [3, 4]]
console.log(a)//1
console.log(b)//2
console.log(c)//3
console.log(d)//4
</script>
3.2对象解构
对象解构是将对象属性和方法快速批量赋值给一系列变量的简洁语法
1.基本语法:
1.赋值运算符=左侧的{ } 用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量
2.对象属性的值将被赋值给与属性名相同的变量
3.注意解构的变量名不要和外面的变量名冲突否则报错
4.对象中找不到与变量名一致的属性时变量值为undefined
<script>
// const obj = {
// uname: 'pink老师',
// age: 18
// }
//解构的语法
const{uname,age}={uname:'pink老师',age:20}
//等价于 const uname = obj.uname
//要求属性名和变量名必须一致才可以
console.log(uname)//pink老师
console.log(age)//20
</script>
给新的变量名赋值:
可以从一个对象中提取变量并同时修改新的变量名
冒号表示 “什么值:赋值给谁”
//给新的变量名赋值
const{uname:username,age}={uname:'pink老师',age:20}
console.log(username)//pink老师
console.log(age)//20
数组对象解构:
<script>
//数组对象解构
const pig = [
{
uname: 'red老师',
age: 20
}
]
const [{ uname, age }] = pig
console.log(uname) //red老师
console.log(age)//20
</script>
多级对象解构:
<script>
const pig = {
name: '佩奇',
family: {
mother: '猪妈妈',
father: '猪爸爸',
sister: '乔治'
},
age: 6
}
//多级对象解构
const {name,family:{mother,father,sister}}=pig
console.log(name)//佩奇
console.log(mother)//猪妈妈
console.log(father)//猪爸爸
console.log(sister)//乔治
</script>
多级数组对象解构:
<script>
const pig = [
{
name: '佩奇',
family: {
mother: '猪妈妈',
father: '猪爸爸',
sister: '乔治'
},
age: 6
}
]
//多级数组对象解构
const [{ name, family: { mother, father, sister } }] = pig
console.log(name)//佩奇
console.log(mother)//猪妈妈
console.log(father)//猪爸爸
console.log(sister)//乔治
</script>
多级对象解构案例:
<script>
// 1. 这是后台传递过来的数据
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 方面后面使用渲染页面
const { data } = msg
console.log(data)
// 需求2: 上面msg是后台传递过来的数据,我们需要把data选出当做参数传递给 函数
// const { data } = msg
// msg 虽然很多属性,但是我们利用解构只要 data值
function render({ data }) {
// const { data } = arr
// 我们只要 data 数据
// 内部处理
console.log(data)
}
render(msg)
// 需求3, 为了防止msg里面的data名字混淆,要求渲染函数里面的数据名改为 myData
function render({ data: myData }) {
// 要求将 获取过来的 data数据 更名为 myData
// 内部处理
console.log(myData)
}
render(msg)
</script>
渲染商品案例:
<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',
},
]
// 1. 声明一个字符串变量
let str = ''
// 2. 遍历数据
goodsList.forEach(item => {
// console.log(item) // 可以得到每一个数组元素 对象 {id: '4001172'}
// const {id} = item 对象解构
const { name, price, picture } = item
str += `
<div class="item">
<img src=${picture} alt="">
<p class="name">${name}</p>
<p class="price">${price}</p>
</div>
`
})
// 3.生成的 字符串 添加给 list
document.querySelector('.list').innerHTML = str
</script>
</body>
4.遍历数组forEach方法(重点)
forEach()方法用于调用数组的每个元素,并将元素传递给回调函数
主要使用场景:遍历数组的每个元素
语法:
被遍历的数组.forEach(function(当前数组元素,当前元素索引号){
//函数体
})
例如:
//forEach 就是遍历 加强版的for循环 适合于遍历数组对象
const arr = ['pink','red','green']
arr.forEach(function(item,index){
console.log(`当前数组元素是:${item}`)
console.log(`当前数组元素的索引是:${index}`)
//与map方法不同的是,forEach没有return
})
注意:
1.forEach主要是遍历数组
2.参数当前数组元素是必须要写的,索引号可选。
5.深入对象
5.1创建对象三种方式
①利用对象字面量创建对象
const o = {
name:'佩奇'
}
②利用new object创建对象
const o = new object({name:'佩奇'})
console.log(o)
③利用构造函数创建对象
function Pig() {
this.uname = uname
//this.属性= 形参
this.age = age
}
console.log(new Pig('佩奇', 6))
5.2构造函数
目标:能够利用构造函数创建对象
构造函数:是一种特殊的函数,主要用来初始化对象
使用场景:常规的{...}语法允许创建一个对象。比如我们创建了佩奇的对象,继续创建乔治的对象还需要重新写一遍,此时可以通过构造函数来快速创建多个类似的对象。
构造函数在技术上是常规函数。
不过有两个约定:
1.它们的命名以大写字母开头。
2.它们只能由"new"操作符来执行。
说明:
1.使用new关键字调用函数的行为被称为实例化
2.实例化构造函数时没有参数时可以省略()
3.构造函数内部无需写return,返回值即为新创建的对象
4.构造函数内部的return返回的值无效,所以不要写return
5.new Object()new Date()也是实例化构造函数
实例化执行过程:
1.创建新空对象
2.构造函数this指向新对象
3.执行构造函数代码,修改this,添加新的属性
4.返回新对象
5.3实例成员&静态成员
实例成员:
通过构造函数创建的对象称为实例对象,实例对象中的属性和方法称为实例成员(实例属性和实例方法)
说明:
1.为构造函数传入参数,创建结构相同但值不同的对象
2.构造函数创建的实例对象彼此独立互不影响
<script>
//实例成员:实例对象上的属性和方法属于实例成员
function Pig(name) {
this.name = name
}
const peiqi = new Pig('佩奇')
const qiaozhi = new Pig('乔治')
peiqi.name = '小猪佩奇' //实例属性
peiqi.sayHi = () => { //实例方法
console.log('hi~~')
}
console.log(peiqi)
console.log(qiaozhi)
console.log(peiqi === qiaozhi)
</script>
静态成员:
构造函数的属性和方法被称为静态成员(静态属性和静态方法)
说明:
1.静态成员只能构造函数来访问
2.静态方法中的this指向构造函数
比如Date.now () Math.PI Math.random()
<script>
//2.静态成员:构造函数上的属性和方法称为静态成员
function Pig(name) {
this.name = name
}
Pig.eyes = 2 //静态属性
Pig.sayHi = function () { //静态方法
console.log(tihs)
}
Pig.sayHi()
console.log(Pig.eyes) //2
</script>
5.4内置构造函数
在JavaScript中最主要的数据类型有6种:
基本数据类型:
字符串、数值、布尔、undefined、null
引用类型:
Object,Array,RegExp,Date
但是,我们会发现有些特殊情况:
//普通字符串
const str = 'andy'
console.log(str.length) //4
其实字符串、数值、布尔、等基本类型也都有专门的构造函数,这些我们称为包装类型。
JS中几乎所有的数据都可以基于构成函数创建。
Object:
Object是内置的构造函数,用于创建普通对象。
//通过构造函数创建普通对象
const user=new object({name:'小明',age:15})
推荐使用字面量方式声明对象,而不是Object构造函数
学习三个常用静态方法(静态方法就是只有构造函数Object可以调用的)
作用:Object.keys静态方法获取对象中所有属性(键)
注意:返回的是一个数组
语法:
<script>
const o = { name: '佩奇', age: 6 }
//获得对象的所有键,并且返回是一个数组
const arr = Object.keys(o)
console.log(arr)//['name','age']
//获得所有的属性值
console.log(Object.values(o)) //['pink','18']
</script>
作用:Object.assign 静态方法常用于对象拷贝
使用:经常使用的场景给对象添加属性
语法:
<script>
const o = { name: '佩奇', age: 6 }
const obj = {}
Object.assign(obj, o)
console.log(obj) //{ name: '佩奇', age: 6 }
//添加属性
Object.assign(o,{gender:'女'}) //{ name: '佩奇', age: 6 ,gender:'女'}
console.log(o)
</script>
Arrary:
Array是内置的构造函数,用于创建数组
const arr = new Array(3,5)
console.log(arr)//[3,5]
创建数组建议使用字面量创建,不用Array构造函数创建
方法 | 作用 | 说明 |
---|---|---|
forEach | 遍历数组 | 不返回数组,经常用于查找遍历数组元素 |
filter | 过滤数组 | 返回新数组,返回的是筛选满足条件的数组元素 |
map | 迭代数组 | 返回新数组,返回的是处理之后的数组元素,想要使用返回的新数组 |
reduce | 累计器 | 返回累计处理的结果,经常用于求和等 |
基本语法:
arr.reduce(function(){},起始值)
arr.reduce(function(上一次值,当前值) {} ,初始值)
参数:如果有起始值,则把初始值累加到里面
<script>
const arr = [1, 5, 8]
//1.没有初始值
const total = arr.reduce(function (prev, current) {
return prev + current
})
console.log(total) //14
//2.有初始值
const total = arr.reduce(function (prev, current) {
return prev + current
}, 10)
console.log(total) //24
//箭头函数写法
const total = arr.reduce((prev, current) => prev + current, 10)
console.log(total)
</script>
reduce的执行过程:1.如果没有起始值,则上一次值以数组的第一个数组元素的值
2.每一次循环,把返回值给做为下一次循环的上一次值
3.如果有起始值,则起始值做为上一次值
计算薪资案例:
<script>
const arr = [
{
name: '张三',
salary: 10000
},
{
name: '李四',
salary: 10000
},
{
name: '王五',
salary: 10000
}
]
//计算薪资案例
const total = arr.reduce((prev, current) => {
return prev + current.salary
}, 0)
console.log(total)
</script>
数组常见方法-其他方法:
- 实例方法 join 数组元素拼接为字符串,返回字符串(重点)
- 实例方法 find 查找元素,返回符合测试条件的第一个数组元素值,如果没有符合条件的则返回undefined(重点)
- 实例方法 every 检测数组所有元素是否都符合指定条件,如果所有元素都通过检测返回true,否则返回false(重点)
- 实例方法some检测数组中的元素是否满足指定条件,如果数组中有元素满足条件返回true,否则返回false
- 实例方法concat合并两个数组,返回生成新数组
- 实例方法sort对原数组单元值排序
- 实例方法splic,删除或替换原数组单元
- 实例方法reverse反转数组
- 实例方法findIndex查找元素的索引值
find 案例:
<script>
const arr = [
{
name: '小米',
salary: 10000
},
{
name: '李四',
salary: 10000
},
{
name: '王五',
salary: 10000
}
]
const mi = arr.find(function (item) {
return item.name === '小米'
console.log(item.name) //小米
})
console.log(mi) // {name: '小米',salary: 10000}
</script>
数组常见方法-伪数组转换为真数组
静态方法Array.from()
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
const lis = document.querySelectorAll('ul li')
const lis2 = Array.from(lis)
lis2.pop()
console.log(lis2)
</script>
</body>
String:
在JavaScript中的字符串、数值、布尔具有对象的使用特征,如具有属性和方法
<script>
//字符串类型的
const str = 'hello world'
//统计字符的长度(字符数量)
console.log(str.length)
//数值类型
const price = 12.234
//保留两位小数
price.toFixed(2)
</script>
之所以具有对象特征的原因是字符串、数值、布尔类型数据是JavaScript底层使用Object构造函数“包装”来的,被称为包装类型。
常见实例方法:
- 实例属性Length用来获取字符串的度长(重点)
- 实例方法split(’分隔符')用来将字符串拆分成数组(重点)
- 实例方法substring(需要截取的第一个字符的索引[,结束的索引号])用于字符串截取(重点)
- 实例方法startsWith(检测字符串[,检测位置索引号])检测是否以某字符开头(重点)
- 实例方法includes(搜索的字符串[,检测位置索引号])判断一个字符串是否包含在另一个字符串中,根据情况返回true或false(重点)
- 实例方法toUpperCase用于将字母转换成大写
- 实例方法toLowerCase用于将字母转换成小写
- 实例方法indexOf检测是否包含某字符
- 实例方法endsWith检测是否以某字符结尾
- 实例方法replace用于替换字符串,支持正则匹配
- 实例方法match用于查找字符串,支持正则匹配
实例方法split(’分隔符')用来将字符串拆分成数组(重点):
<script>
//把字符串 转换为 数组
const str = 'pink,red'
const arr = str.split(',')
console.log(arr) //['pink', 'red']
const str1 = '2022-3-4'
const arr1 = str1.split('-')
console.log(arr1) //['2022', '3', '4']
</script>
实例方法substring(需要截取的第一个字符的索引[,结束的索引号])用于字符串截取(重点)
<script>
//字符串的截取 substring(开始的索引号[,结束的索引号])
//如果省略 结束的索引号,默认取到最后
//结束的索引号不包括想要截取的部分
const str = '今天又要做核酸了'
console.log(str.substring(5, 7)) //核酸
console.log(str.substring(5)) //核酸了
</script>
实例方法startsWith(检测字符串[,检测位置索引号])检测是否以某字符开头(重点)
<script>
var str = 'To be, or not to be, that is ...'
console.log(str.startsWith("To be")) //true
console.log(str.startsWith("not to be")) //true
console.log(str.startsWith("not to be", 10)) //true
</script>
实例方法includes(搜索的字符串[,检测位置索引号])判断一个字符串是否包含在另一个字符串中,根据情况返回true或false(重点)SHEN
const str = "To be, or not to be, that is the question.";
console.log(str.includes("To be")); // true
console.log(str.includes("question")); // true
console.log(str.includes("nonexistent")); // false
console.log(str.includes("To be", 1)); // false
console.log(str.includes("TO BE")); // false
console.log(str.includes("")); // true
6.深入面向对象
6.1编程思想
面向过程介绍:![](https://i-blog.csdnimg.cn/blog_migrate/3489ab373c14c1592c98f82fddb20fa5.png)
面向对象介绍:
在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工。
面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目。
面向对象的特性:
- 封装性
- 继承性
- 多态性
6.2构造函数
封装是面向对象思想中比较重要的一部分,JS面向对象可以通过构造函数实现的封装。
同样的将变量和函数组合到了一起并能通过this实现数据的共享,所不同的是借助构造函数创建出来的实例对象之间是彼此不影响的
总结:
1.构造函数体现了面向对象的封装特性
2.构造函数实例创建的对象彼此独立、互不影响
<script>
function Star(uname, age) {
this.uname = uname
this.age = age
this.sing = function () {
console.log('我会成功')
}
}
//实例对象,获得了构造函数中封装的所有逻辑
const ldh = new Star('刘德华', 18)
const zxy = new Star('张学友', 19)
</script>
6.3原型
原型对象(prototype):
目标:能够利用原型对象实现方法共享
构造函数通过原型分配的函数是所有对象所共享的。
JavaScript规定,每一个构造函数都有一个prototype属性,指向另一个对象,所以我们也称为原型对象
这个对象可以挂载函数,对象实例化不会多次创建原型上函数,节约内存
我们可以把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法。
构造函数和原型对象中的this都指向实例化的对象
<script>
//公共的属性写到构造函数里面
function Star(uname, age) {
this.uname = uname
this.age = age
// this.sing = function () {
// console.log('唱歌')
// }
//公共的方法写到原型对象上
Star.prototype.sing = function () {
console.log('唱歌')
}
}
const ldh = new Star('刘德华', 18)
const zxy = new Star('张学友', 19)
ldh.sing() //唱歌
zxy.sing() //唱歌
console.log(ldh.sing === zxy.sing) //true
</script>
1.原型是什么?
- 一个对象,我们也称为prototype为原型对象
2.原型的作用是什么?
- 共享方法
- 可以把那些不变的方法,直接定义在prototype对象上
3.构造函数和原型里面的this指向谁?
- 实例化的对象
<script>
let that
function Star(uname) {
that = this
this.uname = uname
}
//实例对象 ldh
const ldh = new Star('刘德华')
console.log(that === ldh) //true
</script>
constructor属性:
使用场景:
如果有多个对象的方法,我们可以给原型对象采取对象形式赋值
但是这样就会覆盖构造函数原型对象原来的内容,这样修改后的原型对象constructor就不再指向当前构造函数了
此时,我们可以在修改后的原型对象中,添加一个constructor指向原来的构造函数。
<script>
function Star() {
}
Star.prototype = {
constructor: Star,
sing: function () {
console.log('唱歌')
},
dance: function () {
console.log('跳舞')
}
}
console.log(Star.prototype)
</script>
对象原型(__proto__)
注意:
- __proto__是JS非标准属性
- [[prototype]]和__proto__意义相同
- 用来表明当前实例对象指向哪个原型对象prototype
- __proto__对象原型里面也有一个constructor属性,指向创建该实例对象的构造函数
<script>
function Star() {
}
const ldh = new Star()
//对象原型__proto__ 指向该构造函数的原型对象
console.log(ldh.__proto__ === Star.prototype) //true
//__proto__对象原型里面也有一个constructor属性,指向创建该实例对象的构造函数
console.log(ldh.__proto__.constructor === Star) //true
</script>
1.prototype是什么?哪里来的?
- 原型(原型对象)
- 构造函数都自动有原型
2.constructor属性在哪里?作用干啥的?
- prototype原型和对象原型__proto__里面都有
- 都指向创建实例对象/原型的构造函数
3.__proto__属性在哪里?指向谁?
- 在实例对象里面
- 指向原型prototype
原型继承:
为什么red.__proto__.constructor === Woman打印出来是false?
<script>
//继续抽取 公共的部分放到原型对象上
const Person = {
eays: 2,
head: 1
}
//女人 构造函数 继承 想要继承Person
function Woman(){
}
Woman.prototype = Person
const red = new Woman()
console.log(red.__proto__ === Woman.prototype) //true
console.log(red.__proto__.constructor === Woman) //false
</script>
因为代码中存在一个错误,导致`console.log(red.__proto__.constructor === Woman)`的输出结果为`false`。这是因为 `red.__proto__.constructor` 不再指向 `Woman` 构造函数,而是指向 `Person` 构造函数。
在 JavaScript 中,当你将 `Woman.prototype` 设置为 `Person` 对象时,这实际上改变了 `Woman.prototype` 的整个对象。因此,`Woman.prototype.constructor` 不再指向 `Woman` 构造函数。
为了解决这个问题,可以手动将 `Woman.prototype.constructor` 设置回 `Woman`,如下所示:
<script>
//继续抽取 公共的部分放到原型对象上
const Person = {
eays: 2,
head: 1
}
// 女人 构造函数 继承 想要继承 Person
function Woman() {
}
//通过原型继承 Person
Woman.prototype = Person;
Woman.prototype.constructor = Woman; // 原先的constructor被覆盖了,所以手动设置构造函数
const red = new Woman();
console.log(red) // Woman {eays: 2,head: 1}
console.log(red.__proto__.constructor === Woman); // true
</script>
通过手动设置 `Woman.prototype.constructor`,你可以确保它指向 `Woman` 构造函数,而不是继承的 `Person` 构造函数。这样,`console.log(red.__proto__.constructor === Woman)` 将输出 `true`。
为什么给Woman添加baby属性,Man也会自动被添加上baby属性?
<script>
//继续抽取 公共的部分放到原型对象上
const Person = {
eays: 2,
head: 1
}
// 女人
function Woman() { }
Woman.prototype = Person;
Woman.prototype.constructor = Woman;
//添加一个baby属性
Woman.prototype.baby = function(){}
const red = new Woman();
console.log(red)
//男人
function Man() { }
Man.prototype = Person;
Man.prototype.constructor = Man;
const pink = new Man();
console.log(pink)
</script>
男人和女人都同时使用了同一个对象,根据引用类型的特点,他们指向同一个对象,修改一个就会都影响
需求:男人和女人不要使用同一个对象,但是不同对象里面包含相同的属性和方法
解决方法:使用构造函数,new每次都会创建一个新对象
<script>
//继续抽取 公共的部分放到原型对象上
function Person() {
this.eays = 2,
this.head = 1
}
// 女人
function Woman() { }
Woman.prototype = new Person ();
Woman.prototype.constructor = Woman;
//添加一个baby属性
Woman.prototype.baby = function () { }
const red = new Woman();
console.log(red)
//男人
function Man() { }
Man.prototype = new Person();
Man.prototype.constructor = Man;
const pink = new Man();
console.log(pink)
</script>
原型链
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链
原型链-查找规则
①当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。
②如果没有就查找它的原型(也就是__proto__指向的prototype原型对象)
③如果还没有就查找原型对象的原型(Object的原型对象)
④依此类推一直找到Object为止(null)
⑤__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线
⑥可以使用instanceof运算符用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上
<script>
function Person() {
}
const ldh = new Person()
console.log(ldh instanceof Person) //true
console.log(ldh instanceof Object) //true
</script>
7.深浅拷贝
7.1浅拷贝
对基本数据类型进行值传递,对引用数据类型,使用其引用地址,不拷贝其内容,此为浅拷贝
首先浅拷贝和深拷贝只针对引用类型
浅拷贝:拷贝的是地址
常见方法:
1.拷贝对象:Object.assgin()/ 展开运算符{...obj}拷贝对象
2.拷贝数组:Array.prototype.concat()或者[...arr]
<script>
const obj = {
uname: 'pink',
age: 18
}
//浅拷贝
const o = { ...obj }
console.log(o) //{uname: 'pink', age: 18}
o.age = 20
console.log(o) //{uname: 'pink', age: 20}
console.log(obj) //{uname: 'pink', age: 18}
// assign方法
const o = {}
Object.assign(o, obj)
console.log(o) //{uname: 'pink', age: 18}
o.age = 20
console.log(o) //{uname: 'pink', age: 20}
console.log(obj) //{uname: 'pink', age: 18}
</script>
1.直接赋值和浅拷贝有什么区别?
- 直接赋值的方法,只要是对象,都会相互影响,因为是直接拷贝对象栈里面的地址
- 浅拷贝如果是一层对象,不相互影响,如果出现多层对象拷贝还会相互影响
2.浅拷贝怎么理解?
- 拷贝对象之后,里面的属性值是简单数据类型直接拷贝值
- 如果属性值是引用数据类型则拷贝的是地址
7.2深拷贝
首先浅拷贝和深拷贝只针对引用类型
深拷贝:拷贝的是对象,不是地址
常见方法:
1.通过递归实现深拷贝
函数递归:
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数
- 简单理解:函数内部自己调用自己,这个函数就是递归函数
- 递归函数的作用和循环效果类似
- 由于递归很容易发生“栈溢出”错误(stack overflow),所以必须要加退出条件return
<script>
//递归函数
let i = 1
function fn() {
console.log(`这是第${i}次`)
if (i >= 6) {
return
}
i++
fn()
}
fn()
</script>
2.lodash/cloneDeep
3.通过JSON.stringify(0实现