文章目录
JavaScript进阶(ES6)
1.let & const关键字
1.let
关键字
(1) let不能在声明之前使用
num =10
console.log(num)
let num // 报错 annot access 'num' before initialization
- let没有变量提升
(2) let 不允许重复声明同一个变量
let num = 10;
num = 18;
let num = 20; // 报错 Identifier 'num' has already been declared
console.log(num);
(3) let声明的变量具有块级作用域
function fun() {
let a = 10
console.log(a)
}
fun() // 10
console.log(a) //报错 a is not defined
- let声明的变量只在所在的块级有效
let
和var
的区别:
// 1.var 可以先使用 再声明(不合理)
console.log(num)
var num = 10
// 2.var声明过的变量可以重复声明
var num2 = 10
var num2 = 20
console.log(num2)
// 3.var有变量提升,全局变量,没有块级作用域等
- let就很好的解决了以上var存在的问题
2.const
关键字
(1) const声明的同时必须要赋值
const uname; // 报错
console.log(uname)
(2) 当某个变量永远不会改变的时候,就使用const来声明
const uname = '张三'
console.log(uname) // 张三
- 不需要重新赋值的数据使用const
(3) const声明的变量具有块级作用域
function fun() {
const a = 10
console.log(a)
}
fun() // 10
console.log(a) // a is not defined
let
or const
:
-
变量声明优先使用const
-
如果基本数据类型的值或复杂数据类型的地址发生变化的时候,使用let,如:
let num = 1
num++
console.log(num)
for(let i = 0; i < 10;i++) {
document.write(i)
}
- 使用const声明的复杂数据类型(数组、对象等)里面存储的是地址,只要地址不变,就不会报错,如:
const arr = ['a','b','c']
arr.push('d')
console.log(arr)
const person = {
uname:'张三'
age:18
}
person.address = '北京'
console.log(person)
- 建议数组和对象的声明使用const
3.模板字符串
${表达式}
let age = 18
console.log(`今年${age}岁了`);
2.正则表达式
正则表达式(Regular Expression
)是用于匹配字符串中字符组合的模式。在JavaScript中,正则表达式也是对象通常用来查找
、替换
哪些符合表达式的文本
。
作用:
- 验证表单(匹配)
- 过滤敏感词(替换)
- 字符串中提取我们想要的部分(提取)
1.正则表达式的基本使用
const str = 'javascript是一门编程语言,javascript通常用于web前端开发'
// 1.定义规则
const reg = /javascript/
// 2.检测是否匹配(匹配则返回)
console.log(reg.test(str)) // true
console.log(reg.exec(str)) // Array(1)
-
test()
: 用于判断是否有符合规则的字符串,返回的是布尔值
-
exec()
: 用于查找符合规范的字符串,找到返回的是数组
,否则返回null
2.元字符
元字符
是一些具有特殊含义的字符,可以极大的提高了灵活性和强大的匹配功能,例如英文26个字母,我们使用元字符[a-z]
,使用起来更简洁和灵活。
元字符有: 1.边界符
2.量词
3.字符类
(1).边界符
^
console.log(/^哈/.test('哈')) // true
console.log(/^哈/.test('二哈')) // false
^
:表示匹配行首的文本
$
console.log(/哈$/.test('哈哈')) // true
console.log(/哈$/.test('哈阿')) // false
$
:表示匹配行尾的文本
如果^和$在一起使用,表示必须是精确匹配:
console.log(/^哈$/.test('哈')) // true
console.log(/^哈$/.test('哈哈'))// false
(2).量词
* + ?
// *(重复零次或多次 类似>=0)
console.log(/^哈*$/.test('')) // true
console.log(/^哈*$/.test('哈哈哈')) // true
// + (重复一次或多次 类似>=1)
console.log(/^哈+$/.test('')) // false
console.log(/^哈*$/.test('哈哈哈')) // true
// ? (重复0次或1次 类似0||1)
console.log(/^哈?$/.test('')) // true
console.log(/^哈*$/.test('哈哈哈')) // false
{n} {n,} {n,m}
// {n}(重复n次 类似=n)
console.log(/^哈{3}$/.test('哈哈')) // false
console.log(/^哈{3}$/.test('哈哈哈')) // true
// {n,}(重复n次或更多次 类似>=n)
console.log(/^哈{3,}$/.test('哈哈')) // false
console.log(/^哈{3}$/.test('哈哈哈哈')) // true
// {n,m}(重复n到m次 类似>=n&<=m)
console.log(/^哈{1,3}$/.test('哈哈')); // true
console.log(/^哈{1,3}$/.test('哈哈哈')); // true
console.log(/^哈{1,3}$/.test('哈哈哈哈')); // false
(3).字符类
(1)匹配字符集
[]
console.log(/[abc]/.test('ab')); // true
// 精确匹配 [abc] 只选一个 n选1
console.log(/^[abc]$/.test('ab')); // false
console.log(/^[abc]$/.test('a')); // true
[]
:匹配括号里的任意字符
-
console.log(/^[a-z]$/.test('p')); // true
console.log(/^[A-Z]$/.test('p')); // false
console.log(/^[0-9]$/.test(2)); // true
console.log(/^[a-zA-Z0-9]$/.test(0)); // true
-
:连字符,表示一个范围
^
// 表示除了英文小写字母
console.log(/[^a-z]/.test('a')) // false
^
:取反符,表示除了…以外的字符(这里的^是用在[]内)
.
console.log(/[.a-z]/.test('a')); // true
.
:除换行符之外的任意单个字符
// 腾讯qq号(是从10000开始的)
[/^[1-9][0-9]{4,}$/]
(2)预定类
// \d 匹配0~9的任意数字 相当于[0-9]
// \D 匹配除0-9以外的字符 相当于[^0-9]
// \w 匹配任意的字母,数字和下划线,相当于[A-Za-z0-9_]
// \W 匹配除所有字母、数字和下划线的字符,相当于[^A-Za-z0-9_]
// \s 匹配空格(包括换行符,制表符、空格等),相当于[\t\r\n\v\f]
// \S 匹配非空格的字符.相当于[^\t\r\n\v\f]
// 日期格式
// ^\d{4}-\d{1,2}-\d{1,2}
3.修饰符
/表达式/修饰符
console.log(/^python$/ig.test('Python')) // true
-
i
:ignore的缩写,表示正则匹配字母不区分大小写 -
g
:global的缩写,匹配所有满足正则表达式的结果(全局查找)
4.替换文本
str.replace(/正则表达式/,'替换的文本')
const str = 'java是一门编程语言,Java语言非常优美'
const result = str.replace(/java/ig, 'javascript')
console.log(result)
使用repalce()过滤敏感词:
const text = document.querySelector('textarea');
const btn = document.querySelector('button');
const div = document.querySelector('div');
btn.addEventListener('click', function () {
// console.log(text.value);
div.innerHTML = text.value.replace(/激情|基情/g, '***');
text.value = '';
})
3.js垃圾回收机制
垃圾回收机制简称GC
,js中内存的分配和回收都是自动完成的,内存在不使用的时候会被垃圾回收器自动回收。常见的垃圾回收算法:引用计数法
和标记清除法
。
1.引用计数法
ie采用的引用计数算法,定义"内存不再使用"
,就是看一个对象是否有指向它的引用,没有了就自动回收对象。
const arr = [1, 2, 3]
arr = null
- 引用次数变为了0,垃圾回收机制就自动回收了该对象
- 跟踪记录被引用的次数;如果被引用了一次,那么就记录次数1,多次引用就会累加;如果减少一个引用就减1;如果引用次数为0,则释放内存
引用技术器法存在一个致命问题:嵌套引用(循环引用):
function fun() {
let o1 = {}
let o2 = {}
o1.a = o2
o2.a = o1
return '引用计数无法回收'
}
fun()
- 如果两个对象互相引用,尽管他们已经不再使用,垃圾回收器不会进行回收,造成
内存泄漏
2.标记清除法
标记清除法定义"无法到达的对象"
;就是从根部(在js中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的。
function fun() {
let o1 = {}
let o2 = {}
o1.a = o2
o2.a = o1
return '标记清除法可以回收'
}
fun()
- 全局
gl
来出发,从根部无法进入到函数内部,如无法直接找到函数内部的对象,稍后就会回收 - 从根部扫描对象,能查到的就是还是要使用的,查不到的就进行回收
4.js闭包
闭包概念: 一个函数对周围状态的引用捆绑在一起,内层函数中访问到其他外层函数的作用域
简单理解: 闭包 = 内层函数 + 外层函数的变量
1.闭包的基本格式
// 外层函数
function fun1() {
let a = 10
// 内层函数
function fun2() {
console.log(a)
}
return fun2;
}
// 外部outer
const outer = fun1()
outer() // 10
- 闭包的作用: 封闭数据,提供操作,外部也可以访问函数内部的变量
2.闭包的应用
// 比如,我们要做个统计函数调用次数,函数调用一次,就++
function fun1() {
let i = 0
function fun2() {
i++
console.log(`函数被调用了${i}次`)
}
return fun2
}
const outer = fun1()
outer()
- 实现了
数据私有
,无法直接修改i
,保证了安全 i
是局部变量,但是却没有被回收,因为从根部查找到全局作用域的outer
,继而找到i
,i
在fun2
中还在继续使用到。 这也就是使用闭包可能会产生内存泄漏
5.函数的动态参数和剩余参数
1.动态参数
arguments
function sum() {
let s = 0
for (let i = 0; i < arguments.length; i++) {
s += arguments[i]
}
console.log(s)
}
sum(5, 10)
sum(5, 10, 15)
- 当我们不确定用户传递多少个参数时,使用
argument
动态参数
2.剩余参数
...Args
function getSum(a, b, ...arr) {
console.log(arr) // [] [3]
}
getSum(1, 2)
getSum(1, 2, 3)
- 前面两个参数传递给
a
,b
,后面剩余的参数传递给...arr
- 剩余参数允许我们将一个不定数量的参数表示为一个数组
argument动态参数和剩余参数的区别:
-
剩余参数
...
是语法符号,接收最末位的实参(用于获取多余的实参) -
借助
...
获取的剩余实参,是个真数组
-
arguments获取到的实参是以
伪数组
的形式
6.展开运算符
...
const arr1 = [1, 3, 5]
console.log(...arr1) // 1 3 5
- 展开运算符能将数组展开
展开运算符最典型的应用: 求数组的最大(最小)值,合并数组等
// 求数组最大值
const arr1 = [1, 3, 5]
console.log(Math.max(...arr1)) // 5
// 合并数组
const arr2 = [2, 4, 6]
const arr = [...arr1, ...arr2]
console.log(arr) // [1,3,5,2,4,6]
7.箭头函数
ES6
引入箭头函数的目的: 是为了更简洁的函数写法并且不绑定this,箭头函数的语法比函数表达式更简洁。
使用场景: 箭头函数更适用于那些本来需要匿名函数的地方
(1) 箭头函数基本语法
const 函数名 = (形参) => { 函数体 }
const fn = () => {
console.log(123)
}
fn()
- 箭头函数属于函数表达式的形式,因此也不存在函数提升
(2) 箭头函数省略()和{}
const fn2 = x => console.log(x)
fn2(1)
- 箭头函数形参只有一个时,形参的小括号
()
可以省略(没有参数时,括号不能省略) - 箭头函数函数体只有一行代码时,函数体的大括号
{}
可以省略
(3) 箭头函数省略return
const fn3 = x => x + x
const result = fn3(1)
console.log(result)
- 箭头函数只有一行代码时,可以省略
return
(4)箭头函数可以直接返回一个对象
const fn4 = (uname,age) => ({ uanme: uname,age:age })
const result = fn4('张三',18)
console.log(result)
- 这里的对象是使用
()
包起来,因为函数体的{}和对象的{}冲突了,所以这里使用的是()
(5) 箭头函数的参数
const getSum = (...arr) => {
let sum = 0
for (let i = 0; i < arr.length; i++) {
sum += arr[i]
}
return sum
}
const result = getSum(2, 3, 4)
console.log(result)
- 箭头函数的参数是没有
arguments
动态参数的,但是有剩余参数...args
(6) 箭头函数的this指向
const obj = {
uname: '张三',
sayHi: () => {
console.log(this) // window
}
}
obj.sayHi()
const obj2 = {
uname: '赵四',
sayHi: function () {
// console.log(this)
let a = 10
const fn = () => {
console.log(this) // obj2
}
fn()
}
}
obj2.sayHi()
- 箭头函数不会创建自己的
this
,它只会从自己的作用域链的上一层沿用this
DOM事件的回调函数若使用到this,不推荐使用箭头函数:
btn.addEventListener('click', () => {
console.log(this) // window
})
8.解构赋值
1.数组解构
将数组的单元值快速批量赋值给一系列变量的简洁语法
const arr = [100, 60, 80]
const [max, min, avg] = arr
console.log(max,min,avg) // 100 60 80
- 赋值运算符
=
左侧[]
用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量 - 变量的顺序对应数组单元值的位置依次进行赋值操作
// 数组解构细节
// (1)单元值少,变量多的情况
const [a1, a2, a3] = [1, 2]
console.log(a1, a2, a3) // 1 2 undefined
// (2)单元值多,变量少的情况
const [b1, b2] = [1, 2, 3]
console.log(b1, b2) // 1 2
// (3)单元值多,变量少(可以使用剩余参数)
const [c1, c2, ...c3] = [1, 2, 3, 4]
console.log(c1, c2, c3) // 1 2 [3,4]
// (4)防止undefined传递(可以设置默认值 有相对应的单元值就拿过来,无则使用默认值)
const [d1 = 0, d2 = 0, d3 = 0] = [1, 2]
console.log(d1, d2, d3) // 1 2 0
// (5)按需导入赋值(忽略某些值)
const [e1, e2, , e4] = [1, 2, 3, 4]
console.log(e1, e2, e4) // 1 2 4
// (6)支持多维数组的解构
const arr2 = [1, 2, [3, 4]]
const [f1, f2, [f3, f4]] = arr2
console.log(f1,f2,f3,f4) // 1 2 3 4
2.对象解构
将对象的属性和方法快速批量赋值给一系列变量的简洁语法。
const obj = {
uname: '张三',
age: 18
}
const { uname, age } = obj
console.log(uname,age)
- 赋值运算符
=
左侧的{}
用于批量声明变量,右侧对象的属性值将被赋值给左侧的变量 - 对象的属性值将被赋值给与对象的属性名相同的变量
- 注意解构的变量名不要和外面的变量名冲突否则报错
- 对象中找不到与变量名一致的属性时,变量值为
undefined
(1).修改解构的变量名
旧变量名:新变量名
const obj = {
uname: '张三',
age: 18
}
const uname = 'pcc'
const { uname: username, age } = obj
console.log(username, age) // '张三' 18
- 如果解构的变量名和外面的变量名冲突,可以修改解构的变量名
(2).数组对象解构
const xiao = [
{
uname:'佩恩'
age:18
}
]
const [{uname,age}] = xiao
console.log(uname,age)
(3).多级对象解构
const xiao = {
uname:'晓',
family:{
one:'佩恩',
two:'宇智波鼬',
three:'大蛇丸'
},
age:18
}
const {uname,family:{one,two,three}} = xiao
console.log(uname,one)
- 里面的对象需要指定是哪个对象
9.实例成员和静态成员
实例成员
: 实例对象的属性和方法即为实例成员,使用时通过对象.属性
/对象.方法
静态成员
: 构造函数的属性和方法即为静态成员,使用时通过构造函数.属性
/构造函数.方法
// 构造函数
function Star(uname) {
// 1.实例成员
this.uname = uname
this.sayHi = function () {
console.log('hello')
}
}
const hg = new Star('胡歌')
hg.uname = '胡歌'
hg.sayHi = () => {
console.log('hello everybody')
}
console.log(hg.uname)
console.log(hg.sayHi())
// 2.静态成员
Star.eyes = 2
Star.walk = function () {
console.log(this)
}
console.log(Star.eyes) // 2
Star.walk() // Star()
- 实例对象相互独立,实例成员为当前实例对象使用
- 静态成员只能通过构造函数来访问
- 静态方法中的
this
指向构造函数
10.内置构造函数
javascript中内置构造函数:包装类型
、引用类型
。
-
包装类型:
String
、Number
、Boolean
等 -
引用类型:
Object
、Array
、RegExp
、Date
等
1.Object的静态方法
(1) 获取对象的属性名
Object.keys()
const obj = {
uname: '张三',
age: 18
}
console.log(Object.keys(obj)) // [uname,age]
(2)获取对象的属性值
Object.values()
const obj = {
uname: '张三',
age: 18
}
console.log(Object.keys(obj)) // ['张三',18]
(3)拷贝对象
Object.assign(待拷贝对象,拷贝对象)
const obj2 = {}
// 将obj对象拷贝给obj2对象
Object.assign(obj2, obj)
console.log(obj2) // {uname:'张三',age:18}
拷贝对象的使用场景: 经常使用在给对象添加属性
// 给obj添加sex属性
Object.assign(obj, { sex: '男' })
2.Arrays实例方法
forEach()
const arr = [1, 2, 3, 4]
arr.forEach((ele, index) => {
console.log(ele) // 1 2 3 4
console.log(index) // 0 1 2 3
})
- forEach()有
value
和index
两个参数,value表示当前元素的值,index表示当前元素的下标 - forEach()遍历数组,不改变原数组,经常用于查找遍历数组元素
filter()
const arr1 = [1, 2, 3, 4, 5, 6]
const result = arr1.filter((ele) => {
// 筛选数组中为偶数的元素
return ele % 2 === 0
})
console.log(result) // [2,4,6]
- filter()有
item
,index
,array
参数,item为数组的每个元素,index为数组元素的下标,array为原始数组 - filter()过滤数组,返回新数组,返回的是筛选满足条件的数组元素
- filter()有
return
返回值
map()
const arr2 = [1, 2, 3, 4, 5]
const result = arr2.map((ele, index) => {
return ele + '元素'
})
console.log(result)
- map()有
element
,index
参数,element为数组元素,index为数组元素的下标 - map()迭代数组,返回新数组,返回的是处理之后的数组,想要使用返回的新数组
- map()有
return
返回值
reduce()
// 1.无初始值
const arr1 = [10, 20, 30]
const result = arr3.reduce(function (prev, current) {
return prev + current
})
console.log(result)
// 2.有初始值
const result2 = arr1.reduce((prev, current) => prev + current, 10)
console.log(result3) // 70
- reduce()有
prev
,current
,[初始值]
参数,prev为上一次值,current为当前值 - reduce()可以做累计器,返回累计处理的结果,经常用于求和等
find()
const arr = ['red', 'green', 'blue']
const result = arr.find(function (item) {
return item === 'blue'
})
console.log(result) // blue
- find()查找数组中第一个满足条件的元素的值,并返回
find()应用场景: 比如有很多数据,我们要查找并返回我们想要使用的数据
const phone = [
{
uname: '小米',
price: 1999
},
{
uname: '华为',
price: 3999
},
{
uname: '苹果',
price: 5999
}
]
const total = phone.find(item => item.uname === '苹果')
console.log(total)
- 使用find()方法查找,根据
item.uname==='苹果'
这个关键字条件,查找到该对象,并返回该对象
every()
const arr1 = [10, 20, 30, 40]
const flag = arr1.every(item => item >= 20)
console.log(flag) // false
- every()检测数组所有元素是否都符合指定条件,如果所有元素都符合,则返回true,否则false
some()
const arr = [10, 20, 30, 40]
const flag = arr1.some(item => item >= 20)
console.log(flag) // true
- some()检测数组中的元素是否满足指定条件,如果数组中有元素满足条件,则返回true,否则false
3.String实例方法
includes(要查找的字符串[,检测位置索引号)
const str = 'pancc'
const re = str.includes('q')
console.log(re) // false
const re2 = str.includes('n', 3)
console.log(re2) // flase
- includes()判断一个字符串是否包含在另一个字符串中,根据情况返回布尔值
match(regexp)
const str = "Nothing"
const re = str.match()
console.log(re) // [""]
const str2 = 'javascript'
const reg = /java/
const result =str2.match(reg)
cosole.log(result) // java
- match()用于查找字符串,支持正则匹配,返回一个字符串匹配正则表达式的结果
11.原型
javascript规定,每一个构造函数都有一个prototype
属性,指向另一个对象,所以我们也称为原型对象
。原型对象可以挂载函数
,对象实例化不会多次创建原型上的函数,节约了内存。
我们可以将公共的属性写在构造函数中,公共的方法写在原型prototype中。
1.原型prototype
function Star(uname, age) {
this.uname = uname
this.age = age
}
Star.prototype.sing = funcation() {
console.log('唱歌~')
}
const zjl = new Star('周杰伦', 40)
zjl.sing()
const ljj = new Star('林俊杰', 40)
ljj.sing()
console.log(zjl.sing === ljj.sing)
使用原型扩展数组方法:
// 1.求数组的最大值
const arr = [1, 3, 5, 7, 9]
// 使用原型对象prototype挂载函数(求最大值的方法)
Array.prototype.max = function () {
// 扩展运算符将数组展开
return Math.max(...this) // prototype原型对象中的this指向的也是实例
}
console.log(arr.max()) // 9
// 2.对数组求和
// 使用原型对象prototype挂载函数(求和方法)
Array.prototype.sum = function () {
return this.reduce((prev, current) => prev + current, 0)
}
console.log(arr.sum()) // 25
2.constructor属性
原型对象prototype里面有constructor
属性,constructor属性指向该原型对象的构造函数。
function Dog() {
}
console.log(Dog.prototype) // Dog()
console.log(Dog.prototype.constructor === Dog) // true
- 有了constructor属性,我们就可以知道这个原型对象的构造函数是哪个了
constructor属性的应用:
function Star() {
}
Star.prototype = {
// 添加一个constructor属性来指向他的构造函数
constructor: Star,
sing: function () {
console.log('唱歌')
},
dance: function () {
console.log('跳舞')
}
}
console.log(Star.prototype)
- 如果有多个对象的方法,我们可以给原型对象采取对象赋值的形式,但是这样就会覆盖构造函数原型对象原来的内容,这样修改后原型对象就没有constructor来指向当前的构造函数了
- 我们可以在修改后的原型对象中,添加一个constructor属性来指向他的构造函数
3.对象原型proto
每个实例对象中都有一个__proto__
对象原型,__proto__
指向构造函数的prototype原型对象
function Dog() {
}
Dog.prototype.run = function () {
console.log('二哈在跑')
}
const erha = new Dog()
erha.run()
- 之所以我们的对象实例,能使用到构造函数prototype原型对象的属性和方法,是因为有了
__proto__
对象原型的存在
function Star() {
}
const zjl = new Star()
console.log(zjl.__proto__)
console.log(zjl.__proto__ === Star.prototype) // true
__proto__
也有constructor属性__proto__
指向构造函数的prototype原型对象
prototype
和__proto__
小结:
- prototype是原型对象, 构造函数中自动有原型对象
- prototype原型对象和对象原型
__proto__
里面都有constructor属性,都指向创建实例对象 / 原型的 构造函数 __proto__
属性在实例对象里面, 它指向原型对象prototype
4.原型继承
子类的原型 = new 父类
function Person() {
this.eyes = 2
this.head = 1
}
function Man() {
}
// 原型继承
Man.prototype = new Person()
Man.prototype.constructor = Man // 重新让constructor指回原型的构造函数
const jack = new Man()
console.log(jack.eyes) // 2
console.log(jack.head) // 1
- 通过原型继承
Person
,就能使用到父类的属性和方法
5.原型链
基于原型对象的继承使得不同构造函数的对象关联在一起,并且使这种关联的关系是一种链状的结构,我们将原型对象的链状结构
关系称为原型链。
function Star() {
}
const zjl = new Star()
console.log(zjl.__proto__ === Star.prototype) // true
// 构造函数中的原型对象也是对象,所以里面也有__proto__
console.log(Star.prototype.__proto__ === Object.prototype) // true
console.log(Object.prototype.__proto__) // null
- 原型链也就是一种
查找规则
- 当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性
- 如果没有就查找它的原型(也就是
__proto__
指向的prototype
原型对象) - 如果还没有就查找原型对象的原型(Object的原型对象)
- 依次类推直到找到
Object
为止(null) __proto__
对象原型的意义就在于为对象成员查找机制提供一个方向
使用instanceof
运算符用于检测构造函数中的prototype属性是否出现在某个实例对象的原型链上:
console.log(zjl instanceof Star) // true
console.log(zjl instanceof Object) // true
console.log(zjl instanceof Array) // false
12.递归函数
如果一个函数在内部可以调用其本身,那么这个函数就是递归函数(自己调自己)
let i = 1
function fun() {
console.log(`正在打印第${i}次`)
if (i >= 6) {
return
}
i++
fun()
}
fun()
13.浅拷贝&深拷贝
1.浅拷贝
使用浅拷贝拷贝数据时,如果是简单数据类型
拷贝的就是值
,如果是复杂数据类型
拷贝的就是地址值
。
拷贝对象:
-
Object.assign()
-
{ ...obj }
拷贝数组:
Array.prototype.concat()
[...arr]
const obj = {
uname: '张三',
age: 18,
family: {
uname: '王五'
}
}
const o2 = {}
Object.assign(o2, obj)
// 简单数据类型
o2.age = 19
console.log(o2) // 19
console.log(obj) // 18
// 复杂数据类型
o2.family.uname = '赵四'
console.log(o2) // 赵四
console.log(obj) // 赵四
- 以上可以看出拷贝复杂数据类型时,还是指向的同一个地址,操作同一个对象。
2.深拷贝
深拷贝拷贝的直接就是对象
,而不是地址。
三种方式:
- 通过
递归
实现深拷贝 lodash/cloneDeep
JSON.stringify()
(1)递归函数实现深拷贝
const obj2 = {
uname: '刘备',
age: 18,
hobby: ['乒乓球', '篮球'],
family: {
wujiang: '关羽'
}
}
const o3 = {}
// 递归函数
function deepCopy(newObj, oldObj) {
for (let k in oldObj) {
// 处理数组问题
if (oldObj[k] instanceof Array) {
newObj[k] = []
deepCopy(newObj[k], oldObj[k]) // 递归
// 处理对象问题
} else if (oldObj[k] instanceof Object) {
newObj[k] = {}
deepCopy(newObj[k], oldObj[k]) // 递归
} else {
// newObj[k] === o3.uname (给新对象添加属性)
newObj[k] = oldObj[k]
}
}
}
deepCopy(o3, obj2)
o3.age = 20
o3.hobby[0] = '游泳'
o3.family.wujiang = '张飞'
console.log(o3)
console.log(obj2)
- 深拷贝就是拷贝出来的新对象不会影响到旧对象,要想实现深拷贝,可以使用到递归函数
- 如果拷贝的是简单数据类型的时候, 直接赋值就可以了。但是遇到数组的,再次调用这个递归函数
- 如果遇到的是对象的形式,再次调用这个递归函数。
- 先处理
Array
后处理Object
(2)利用lodash实现深拷贝
_.cloneDeep(拷贝的对象)
<script src="./lodash/lodash.min.js"></script>
<script>
const obj = {
uname: '刘备',
age: 18,
hobby: ['三顾茅庐', '如鱼得水'],
family: {
wujiang: '关羽'
}
}
const o = _.cloneDeep(obj)
o.family.wujiang = '诸葛亮'
console.log(o)
console.log(obj)
</script>
(3) 利用JSON实现深拷贝
const obj = {
uname: '刘备',
age: 18,
hobby: ['三顾茅庐', '如鱼得水'],
family: {
wujiang: '关羽'
}
}
const o = JSON.parse(JSON.stringify(obj))
o.family.wujiang = '诸葛亮'
console.log(o)
console.log(obj)
- 将对象转换为JSON字符串(得到的是简单数据类型)
- 再将JSON字符串转换为对象(得到的是新的对象)
14.异常处理
异常处理是指预估代码执行过程发生错误,然后最大程度的避免错误的发生导致整个程序无法继续运行。
(1).throw
抛异常
function fun(x, y) {
if (!x || !y) {
// throw '用户没有传参'
throw new Error('用户没有传参')
}
return x + y
}
console.log(fun())
- throw抛出异常,程序也会终止执行
- throw后面跟的是错误信息
- Error对象配合throw使用,能够设置更详细的错误信息
(2).try/catch
捕获异常
try {
const p = document.querySelector('.div')
p.style.color = 'deeppink'
} catch (err) {
console.log(err.message)
throw new Error('你看看,我就说标签写错了把')
// return
}
finally {
console.log('执行')
}
- try块中放可能出现的错误代码
- 若try块的代码出错,则会执行catch块,并捕获到错误信息
- catch中提示的是浏览器的错误信息,但是不会中断程序的执行,想要中断可以使用return,也可以搭配throw
- finally块中不管程序有没有出错,都会执行
(3).debugger
调试
const arr2 = [2, 4, 6];
const newArr = arr2.map(function (ele, index) {
debugger
console.log(ele); // 2 4 6
console.log(index); // 0 1 2
return ele + '块钱';
})
console.log(newArr);
- debugger可以在代码上打上断点
15.this指向
使用call(),apply(),bind()都可以改变this的指向。
call(thisArg,arg1,arg2...)
const obj = {
uname: '张三',
age: 18
}
function fn(x, y) {
console.log(this)
console.log(x + y) // 3
}
fn.call(obj, 1, 2) // fn的this指向了obj
apply(thisArg,[argArray])
fn.apply(obj2, [1, 2]) // // fn的this指向了obj
apply()的应用场景:
// 求数组的最大值
const arr = [1, 2, 3, 4, 5]
const max = Math.max.apply(null, arr) // null表示this的指向为null
console.log(max)
// console.log(Math.max(...arr))
- apply()使用经常跟数组有关系
bind(thisArg,arg1,arg2...)
const obj3 = {
uname: '关羽',
age: 23
}
function fn3() {
console.log(this)
}
const fun = fn3.bind(obj3)
console.log(fun)
fun()
- 返回的是一个函数(通过拷贝原函数得到的函数,这个函数里面的this是更改过的)
bind改变定时器内部的this指向:
// 需求: 有一个按钮,点击后就禁用,2秒后开启
const btn = document.querySelector('button')
btn.addEventListener('click', function () {
this.disabled = true
setTimeout(function () {
this.disabled = false
}.bind(btn), 2000)
})
- 定时器this原本指向window 改变this指向btn
call(),apply,bind三者的区别:
-
call()和apply()都会调用函数,bind不会调用函数
-
call()和apply()传递的参数不一样,call传递的是普通参数的形式,apply传递的参数是数组
16.性能优化
1.防抖(debounce)
防抖也就是指触发事件在n秒内函数只能执行一次,如果在n秒内事件又触发了,则会重新计算函数执行时间。
防抖(debounce
): 单位时间内,频繁触发事件,只执行最后一次。
使用场景:
- 搜索框搜索输入,手机号、邮箱验证输入等(只需要用户最后一次输入完,再发送请求)
// 利用防抖实现性能优化
// 需求: 鼠标在盒子上移动,里面的数字就会变化+1
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = i++
}
// lodash库实现防抖(500毫秒之后才去+1)
// _.debounce(func,时间)
box.addEventListener('mousemove', _.debounce(mouseMove, 500))
- 如果里面存在大量消耗性能的代码,如dom操作,数据处理等可能会造成卡顿,此时就需要用到防抖
2.节流(throttle)
节流指连续触发事件但是在n秒内只执行一次函数。比如在1000ms内,不管触发多少次事件,只执行一次(在这个时间内,不会被打断,只执行当前这一次)。
节流(throttle
):单位时间内,频繁触发事件,只执行一次。
使用场景:
- 鼠标移动
mousemove
,页面尺寸缩放resize
,滚动条滚动scroll
等
// 利用节流实现性能优化
// 需求: 鼠标在盒子上移动,里面的数字就会变化+1
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = i++
}
// 利用lodash库实现节流(1000毫秒之后才去+1)
// _.throttle(func,时间) 在wait秒内最多执行func一次
box.addEventListener('mousemove', _.throttle(mouseMove, 1000))
节流案例:
<script src="./lodash/lodash.min.js"></script>
<video src=""></video>
<script>
// 案例: 页面打开,就可以记录上一次的视频播放位置
// 1.获取元素,要对视频进行操作
const video = document.querySelector('video')
video.ontimeupdate = _.throttle(() => {
// 把当前的时间存储到本地
localStorage.setItem('currentTime', video.currentTime)
}, 1000)
// 2.打开页面触发事件,就从本地存储里面取出记录的时间
video.onloadeddata = () => {
video.currentTime = localStorage.getItem('currentTime') || 0
}
</script>
- 在
ontimeupdate
事件触发的时候,每隔1秒钟,就记录当前时间到本地存储 - 下次打开页面,
onloadeddata
事件触发,从本地存储取出时间,让视频从取出的时间播放,如果没有就默认为0s
bounce`): 单位时间内,频繁触发事件,只执行最后一次。
使用场景:
- 搜索框搜索输入,手机号、邮箱验证输入等(只需要用户最后一次输入完,再发送请求)
// 利用防抖实现性能优化
// 需求: 鼠标在盒子上移动,里面的数字就会变化+1
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = i++
}
// lodash库实现防抖(500毫秒之后才去+1)
// _.debounce(func,时间)
box.addEventListener('mousemove', _.debounce(mouseMove, 500))
- 如果里面存在大量消耗性能的代码,如dom操作,数据处理等可能会造成卡顿,此时就需要用到防抖
2.节流(throttle)
节流指连续触发事件但是在n秒内只执行一次函数。比如在1000ms内,不管触发多少次事件,只执行一次(在这个时间内,不会被打断,只执行当前这一次)。
节流(throttle
):单位时间内,频繁触发事件,只执行一次。
使用场景:
- 鼠标移动
mousemove
,页面尺寸缩放resize
,滚动条滚动scroll
等
// 利用节流实现性能优化
// 需求: 鼠标在盒子上移动,里面的数字就会变化+1
const box = document.querySelector('.box')
let i = 1
function mouseMove() {
box.innerHTML = i++
}
// 利用lodash库实现节流(1000毫秒之后才去+1)
// _.throttle(func,时间) 在wait秒内最多执行func一次
box.addEventListener('mousemove', _.throttle(mouseMove, 1000))
节流案例:
<script src="./lodash/lodash.min.js"></script>
<video src=""></video>
<script>
// 案例: 页面打开,就可以记录上一次的视频播放位置
// 1.获取元素,要对视频进行操作
const video = document.querySelector('video')
video.ontimeupdate = _.throttle(() => {
// 把当前的时间存储到本地
localStorage.setItem('currentTime', video.currentTime)
}, 1000)
// 2.打开页面触发事件,就从本地存储里面取出记录的时间
video.onloadeddata = () => {
video.currentTime = localStorage.getItem('currentTime') || 0
}
</script>
- 在
ontimeupdate
事件触发的时候,每隔1秒钟,就记录当前时间到本地存储 - 下次打开页面,
onloadeddata
事件触发,从本地存储取出时间,让视频从取出的时间播放,如果没有就默认为0s