作用域
作用域
目标:了解作用域对程序执行的影响及作用域链的查找机制,使用闭包函数创建隔离作用域避免全局变量污染。
作用域(scope) 规定了变量能够被访问的“范围”,离开了这个“范围”变量便不能被访问
scope样式是有范围的
作用域分为:
局部作用域(块级作用域和函数作用域)
全局作用域
局部作用域
局部作用域分为函数作用域和块作用域
1.函数作用域:
在函数内部声明的变量只能在函数内部被访问,外部无法直接访问。
函数执行完毕,里面的变量会被回收,外部无法访问
总结:
1.函数内部声明的变量,在函数外部无法被访问
2.函数的参数也是函数内部的局部变量
3.不同函数内部声明的变量无法互相访问
4.函数执行完毕后,函数内部的局部变量实际被清空了
2.块作用域:
在 JavaScript 中使用{}包裹的代码称为代码块,代码块内部声明的变量外部将[有可能] 无法被访问.
var不存在块级作用域
var是全局变量
总结:
1.let 声明的变量会产生块作用域,var 不会产生块作用域(var是全局变量)
2.const 声明的常量也会产生块作用域
3.不同代码块之间的变量无法互相访问
4.推荐使用 let 或 const
全局作用域
()
// <script>标签 和 js 文件 的[最外层]就是所谓的全局作用域,在此声明的变量在函数内部也可以被访问.全局作用域中声明的变量,任何其它作用域都可以被访问
注意:
1.为 window 对象动态添加的属性默认也是全局的,不推荐!
2. 函数中未使用任何关键字声明的变量为全局变量,不推荐!! !
function fn() {
num=100
}
num是全局变量
3.尽可能少的声明全局变量,防止全局变量被污染
可能会重名–>报错
作用域链
作用域链本质上是底层的变量查找机制(就近原则)
在函数被执行时,会优先查找当前函数作用域中查找变量
如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
结果a=2查找机制(就近原则)
查找机制
作用域链本质上是底层的变量查找机制。
在函数被执行时,会优先查找当前函数作用域中查找变量
如果当前作用域查找不到则会依次逐级查找父级作用域直到全局作用域
总结:
1.嵌套关系的作用域串联起来形成了作用域链
2.相同作用域链中按着从小到大的规则查找变量
3.子作用域能够访问父作用域,父级作用域无法访问子级作用域
JS垃圾回收机制
垃圾回收器是自己完成回收工作
目标: 了解JS垃圾回收机制的执行过程学习
目的: 为了闭包做铺垫学习路径:
1.什么是垃圾回收机制
2.内存的声明周期
3.垃圾回收的算法说明
例
函数调用完在用完后局部变量就会被清空
内存会被释放
1.什么是垃圾回收机制?
垃圾回收机制(Garbage Collection)简称 GCJS中内存的分配和回收都是自动完成的,内存在不使用的时候套被垃圾回收器自动回收。正因为垃圾回收器的存在,许多人认为JS不用太关心内存管理的问题
但如果不了解JS的内存管理机制,我们同样非常容易成内存泄漏(内存无法被回收) 的情况
不再用到的内存,没有及时释放,就叫做内存泄漏
3.垃圾回收算法说明
所谓垃圾回收,核心思想就是如何判断内存是否已经不再会被使用了,i如果是,就视为垃圾,释放掉
下面介绍两种常见的浏览器垃圾回收算法: 引用计数法 和 标记清除法
引用计数
IE采用的引用计数算法,定义“内存不再使用”的标准很简单,就是看一个对象是否有指向它的引用。
存在bug:对象相互嵌套,每个对象的引用计数永远不为0
算法:
1.跟踪记录每个值被引用的次数。
2.如果这个值的被引用了一次,那么就记录次数1
3.多次引用会累加。
4.如果减少一个引用就减1。
5.如果引用次数是0 ,则释放内存.
例1
如果这个值的被引用了一次,那么就记录次数1
如果减少一个引用就减1。
如果引用次数是0 ,则释放内存.
例2
调用完函数后局部变量会被清空(回收)
存在bug:对象相互嵌套,每个对象的引用计数永远不为0
没人用了 但 垃圾不会被回收–>内存泄漏
标记清除法
现代的浏览器已经不再使用引用计数算法了
现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。
核心:
1.标记清除算法将“不再使用的对象”定义为“无法达到的对象”
2.就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。 凡是能从根部到达的对象,都是还需要使用的
3.那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收
例1
window访问不到,就会被清除
例2
从根部(arr–>0x7766–>0x5544)能访问到0x5544 所以不会被认为是垃圾 不会被回收
标记清除法 从根部出发,看看能不能访问到
小细节:全局变量都不会被回收
局部变量在函数结束的时候,会被回收
闭包
都需要函数,需要的这个函数都不是立马执行的
什么时候会使用闭包?
- 我们需要创造出一个函数. 我们需要给这个函数未来被调用的时候,存储一些数据
- 这个函数不是现在调用,是以后调用的
作用
- 让外部可以访问到函数内部的局部变量
特点
-它是一个函数
-它需要返回一个函数
-返回的这个函数需要使用到外层函数的局部变量
缺点
- 延长了局部变量的生命周期,让局部变量在函数结束的时候,也不会被回收了
-如果闭包使用的过多了,就会造成内存泄漏的问题
闭包的 场景1:给定时器传参
函数怎么来:自己写匿名函数,调用一个函数,得到另一个函数
闭包的 场景2:给事件处理函数传参
闭包的 场景3:点击的节流的效果
节流阀
定义太多变量了
闭包: 可以实现对节流效果的封装
在函数体中,如果有一些东西确定不了,用参数来表示
闭包: 可以实现对节流效果的封装
<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
<button id="btn3">按钮3</button>
<script>
// 练习1: 如何实现按钮1点击的节流的效果
// 练习2: 如何实现按钮1,按钮2,按钮3 点击的节流的效果
const btn1 = document.querySelector('#btn1')
const btn2 = document.querySelector('#btn2')
const btn3 = document.querySelector('#btn3')
function fn(aa) {
let falg = true
return function () {
if (falg) {
falg = false
setTimeout(function () { falg = true }, 3000)
aa()
}
}
}
btn1.addEventListener('click', fn(function () {
console.log('btn1……')
console.log('btn1……')
}))
btn2.addEventListener('click', fn(function () {
console.log('btn2……')
console.log('btn2……')
}))
btn3.addEventListener('click', fn(function () {
console.log('btn3……')
console.log('btn3……')
}))
</script>
预解析
var存在预解析, const和let不存在
我们可以先使用变量,再定义
函数的预解析
可以先调用,再定义
普通的具名函数具备预解析
function fn() {}
我们可以先调用函数,再定义函数
动态参数 arguments
动态参数:arguments是函数内部内置的伪数组变量,它包含了调用函数时传入的所有实参
arguments:存储了所有的实参,所形成的伪数组
总结:
1.arguments 是一个伪数组,只存在于函数中
2.arguments 的作用是动态获取函数的实参
3.可以通过for循环依次得到传递过来的实参
// 写一个函数, 接收一个参数, 这个参数是一个数组,求这个数组中的最大值
// 写一个函数, 接收n个参数, n不确定, 求这n个数中的最大值(使用两种方式, 动态参数和剩余参数)
//动态参数
// 求arguments这个数组的最大值
function getMax() {
let max = arguments[0]
for (let i = 1; i < arguments.length; i++) {
if (arguments[i] > max) {
max = arguments[i]
}
}
console.log(max)
}
getMax(10, 12, 7, 18)
getMax(10, 12)
剩余参数
目标: 能够使用剩余参数
剩余参数允许我们将一个不定数量的参数表示为一个数组
//剩余参数:真数组
function fn(a, b,...c)
console.log(c)
}
fn(1,2,3)//...c:[3]
fn(1,2,3,4)//...C:[3,4]
fn(1, 2,3,4,5)//...c:[3,4,5]
<script>
// 写一个函数, 接收一个参数, 这个参数是一个数组,求这个数组中的最大值
// 写一个函数, 接收n个参数, n不确定, 求这n个数中的最大值(使用两种方式, 动态参数和剩余参数)
//剩余参数
function getMax(...a) {
let max = a[0]
for (let i = 1; i < a.length; i++) {
if (a[i] > max) {
max = a[i]
}
}
console.log(max)
}
getMax(10, 12, 7, 18)
getMax(10, 12)
</script>
展开运算符
1.使用在形参上,代表这个形参是剩余参数
2.使用在数据前面,代表要把这个数据展开
-求数组的最大值Math.max(...数组)
-合并两个数组
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
const arr3 = [...arr1, ...arr2]
-合并两个对象
const obj1 = (name: 'zs')
const obj2 = {age: 18)
const obj3 = {...obj1, ...obj2 }
例:求数组的最大值Math.max(…数组)
<script>
const arr = [10,8,17,5,23,15,6,8]
// // 使用Math中的方法,求arr的最大值
// console.log(Math.max(arr)) // 这行代码为什么会得到NaN
//console.log(Math.max(1,8,17,5,23,15,6))
// console.log(Math.max([1,8,17,5,23,15,6]))
// Math.max(arr[0],arr[1], arr[2], arr[3], arr[4],arr[5], arr[6])
// for (let i=;i<arr.length;i++) {
// Math.max(arr[i])
// }
console.log(Math.max(...arr))
</script>
对象存的是地址
箭头函数 this
在箭头函数出现之前,每一个新函数根据它是被如何调用的来定义这个函数的this值, 非常令人讨厌。
箭头函数不会创建自己的this,它只会从自己的作用域链的上一层沿用this。
全新的函数的写法: 箭头函数
学习路径: 先写普通函数,把普通函数改造为箭头函数,改造习惯了之后,以后我们写函数就直接写箭头函数
把普通函数改造为箭头函数是有固定步骤的:
1.把function去掉
2.在小括号和大括号之间,加上箭头
3.优化:
3.1.如果参数只有一个,小括号可以去掉
3.2.如果函数体只有一行代码,并且是return.大括号可以去掉,return可以去掉
3.3如果return的是一个对象,这时候,如果把函数的{}去掉,return去掉,我们需要在return的这个对象外加上小括号
箭头函数和普通函数的差别
箭头函数没有argume
1.动态参数的姜别那如果箭头函数的实参
- 箭头函数没有argume
- 那如果箭头函数的实参数量不确定 —> 使用剩余参数
2.this的差别
-
普通的函数
谁点出了这个函数进行调用,函数体内部的this就是谁
定时器函数中的this–>window—>setTimeout(function(){console.log(this)},1000)
事件处理函数中的this—>绑定事件的那个元素 -
箭头的函数
箭头函数中的this,是看这个箭头函数在哪里定义的–>绑定事件的那事
无论这个箭头函数怎么调用
箭头函数内部的this,都是定义这个箭头函数时,当前作用域的this
箭头函数内部其实没有this,它所使用的是父级作用域下的this -
构造函数
-
改变指向函数
例题
方式1
方式2
方式3
方式4
方式4改造为箭头函数
reduce
reduce数据的累加
reduce数据的累加改造为箭头函数
reduce对象
方式5
解构
解构赋值
数组解构 []=数组
数组解构是将数组的单元值快速批量赋值给一系列变量的简洁语法。
基本语法:
- 赋值运算符 = 左侧的 [] 用于批量声明变量,右侧数组的单元值将被赋值给左侧的变量
- 变量的顺序对应数组单元值的位置依次进行赋值操作
两个变量的互换
//在js中,一定要在代码前面加分号的两种情况:
// 1,立即执行函数,在立即执行函数前面加上分号
// 2,使用数组解构交换两个变量值的时候
let num1 = 100
let num2 = 200
;[num2, num1] = [num1, num2]//交换了num1和num2的数据
console.log(num1)
console.log(num2)
const arr = [100, 60, 80]
// 练习1: 将arr中的第0个元素赋值给max变量, arr中的第1个元素赋值给min变量, arr中的第2个元素赋值给avg变量
// const max = arr[0]
// const min = arr[1]
// const avg = arr[2]
// 练习2: 将练习1中的代码改造为数组解构的写法
// = 号左边的数组意味: 我要声明多个变量
const [max, min, avg] = arr
在js中,一定要在代码前面加分号的两种情况:
1,立即执行函数,在立即执行函数前面加上分号
2,使用数组解构交换两个变量值的时候
什么东西也会存在一一对应的情况?
实参传递给形参
实参比形参更多:·多出来的实参·没人要
形参比实参更多:多出来的形参 ·为undefined
形参增加默认值
对象的解构 {}=对象
const obj = {
uname:'小明',
age: 18
}
// 如何将obj的uname赋值给一个变量uname
const uname = obj .uname
// 如何将obj的age赋值给一个变量age
const age = obj.age
// 如何将obj的uname赋值给一个变量myName
const myName = obj.uname
// 如何将obj的age赋值给一个变量myAge
const myAge = obj.age
//解构
const(uname : myName, age : myAge} = obj
解构数组套对象
const pig = [
{
aname:佩奇
height: 6
}
]
// 如何将佩奇赋值给aname变量,把6赋值给height变量
const [{aname: a, height: b]] = pig
//将佩奇赋值给a,将6赋值给b
console.log(a)
console.log(b)
在新版es的语法中 (es6),如果,对象的属性名,和属性值的写法 一模一样,这时候,可以进行省略
// 对象的简写形式
const uname = 'Zs'
const age = 18
const obj3 = {
uname: uname,
age: age
}
// 在新版es的语法中 (es6),如果,对象的属性名,和属性值的写法 一模一样,这时候,可以进行省略
const obj3 = {
uname,
age
}
JavaScript高级-2023.4.13
构造函数
作用: 创建对象的
创建对象方式
- 方式1:字面量 创建多个同类型对象的时候, 字面量无法保证每个对象的规格完全一致
- 方式2: new Object()
- 方式3: 构造图数
使用字面量的方式创建一个对象
//1.使用字面量的方式创建一个对象,uname是张三,age是18,height是180
const obj = {
uname:"张三
age: 18,
height: 180
}
// 2.给这个对象增加属性 weight是80
obj.weight = 80
//3.对这个对象进行循环遍历
// const newObj = {}
// for (let k in obj) [
// k obj[k]
// newObj[k] = obj[k]
// }
// 将上面的对象完整的复制一份,形成一个新的对象
const newObj = {
...obj
}
构造函数
function 函数名字 () {}
构造函数要进行对象的创建,需要通过一个关键字:new
function Person(uname, age, weight, height) {
this .uname uname
this.age = age
this .weight = weight
this.height= height
}
const p1 = new Person('张三',18,80,170)
const p2 = new Person('李四',22,75,190)
const p3 = new Person('王五',19,70,180)
// this的指向:
//普通函数: 函数的用者
//箭头函数:函数的定义的地方的this
// 构造函数: 刚刚new出来的对象
// new 构造函数() 内部做了什么事情?
// 1.创建新的对象
// 2.参数的传递: 函数体中的this指的是刚刚new出来的新对象
// 3.通过this关键字,给这个对象增加一些属性
// 4.把this所代表的东西 返回,注我们是不需要自己写return的
构造函数在技术上是常规函数 不过有两个约定:
1.它们的命名以大写字母开头
2它们只能由“new”操作符来执行
说明:
1.使用 new 关键字调用函数的行为被称为实例化
2. 实例化构造函数时没有参数时可以省略 ()
3.构造函数内部无需写return,返回值即为新创建的对象
4.构造函数内部的 return 返回的值无效,所以不要写return
5.new Object () new Date () 也是实例化构造函数
总结
1.什么是实例成员?
实例对象的属性和方法即为实例成员
2.什么是静态成员?
构造函数的属性和方法被称为静态成员
内置构造函数
1.Object.keys(对象)用于获取对象当中所有属性的属性名(数组)
2.Object.values(对象)用于获取对象中所有属性对应的属性值(数组)
3.Object.assign(目标对象, 源对象) 拷贝复制(后面的给前面)
const o = [ uname: 'pink', age: 18 ]
// 练习1.获得所有的属性名console.log(Object.keys(o))
// 练习2.获得所有的属性值console.log(Object.values(o))
// 练习3: 对o进行循环遍历,不使用for in
Object.keys(o).forEach(function(item){
console.log(o[item])
})
Object.values(o).forEach(function(item){
console.log(item)
})
// 练习4.对o进行拷贝,产生一个新的对象
const o2 = {
...O
}
console.log(o === o2)
//Object.assign(目标对象, 源对象)
const o2 = {
// object.assign(目标对象,源对象)
Object.assign(o2,o)
oconsole.log(o2)
console.log(o === o2)
Array
数组构造函数点出的方法–>Array.from(伪数组) --> 使用伪数组生成一个真的数组
数组常见方法- 伪数组转换为真数组静态方法 Array.from()
String
JavaScript高级-2023.4.14
字符串的replaceAll这个方法,在不同的平台下,在node的平台下,字符串是没有replaceAll的方法
在浏览器的环境下,字符串是有replaceAll的方法
在不同的平台下,字符串都具备replace方法
编程思想
面向过程介绍
以功能实现的整个过程做为思考点一步一步怎么实现这个功能
第一步要干嘛
第二步要干嘛
第三步要干嘛
面向对象介绍
面向对象考虑的是 谁能够把这件事情做出来
厨师能够做这件事情function 厨师() {}
const cl = new 厨师()
c1.西红柿炒鸡蛋()
在面向对象程序开发思想中,每一个对象都是功能中心,具有明确分工。
面向对象编程具有灵活、代码可复用、容易维护和开发的优点,更适合多人合作的大型软件项目。
面向对象的特性:
封装性
继承性
多态性
编程思想-面向过程和面向对象的对比
生活离不开蛋炒饭,也离不开盖浇饭,选择不同而已,只不过前端不同于其他语言,面向过程更多
面向过程编程
优点:姓能比面向对象高,适合跟硬件联系很紧密的东西,例如单片机就采用的面向过程编程
缺点:没有面向对象易维护、易复用、易扩展
面向对象编程
优点: 易维护、易复用、易扩展,由于面向对象有封装继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护
缺点:性能比面向过程低
结论 :我们想给每一个实例对象增加方法,找构造函数的原型对象
问题1: 我们怎么给每个实例对象,增加功能?
在构造函数中 this.xxx = function()
但是使用这种方法,就会造成每个对象中的方法都是新的函数,这些函数的功能是完全一样。就比较浪费内存
原型:作用:解决通过构造函数,给每一个实例对象增加功能时,内存空间浪费的问题
Star.prototype构造函数的原型对象
consloe.log(Star.prototype)//Obj
Star.prototype.xxx=‘hello’// xxx:hello
在js中规定:在构造函数的原型对象中,拥有的东西,可以被实例对象所访问
增加一个属性
- 但是: 为啥? 因为实例对象中 有 _proto 指向了构造函数的原型对象
所以,我们以后,想给每个实例对象增加功能
找到 构造函数的 原型对象,给原型对象增加方法
s1:对象
s1.proto_ :对象的原型
构造函数
构造函数的原型对象
实例对象
实例对象的原型
实例对象的原型就是 构造函数的原型对象
让arr1可以点出max方法和min方法,得到数组中的最大值和最小值
我们自己给每个数组增加myfilter方法,效果类似于 filter 的方法
调用这个方法的时候,告诉规则
把函数当参数传递
数组的mymap()
1.找构造函数的原型对象,给原型对象增加方法
2.在增加的这个方法中,this代表啥
给数组扩展方法 模拟slice myslice
Array.prototype.myslice = function(start, end){
const newArr =[]
for (let i=start;i<end;i++) {
(newArr.push(this[i])
}
return newArr
}
const arr6 = [1,20, 3,40, 60,50]
// arr6.slice(2,4)
// myslice
console.log(arr6.myslice(2,5))
构造函数的原型对象
原型对象的查找机制
作用域链 变量的查找机制
原型链 实例对象属性的查找机制
constructor的意义: 表名了我是谁的原型对象
对象.一个属性,这个属性将会在这条链上进行寻找—>就近原则
整条链上都没有这个属性,将会undefined
实例对象都会有__proto__
Dog.protoType = Animal.protoType 实现继承效果
实现继承的方法
JavaScript高级-2023.4.15
给每一个实例对象增加方法
提示案例
浅拷贝
const obj1 = {
uname: 'pink',
age: 18
}
//使用多种方式将obj1拷贝一份出来,形成一个新的对象
// 方式1: 扩展运算符
const obj2 = {
...obj1
}
obj2.age = 20
console.log(obj1.age)//18
// 方式2: for循环
const obj2 = {}
for(let k in obj1){
obj2[k]=obj1[k]// obj2[ 'uname'] = pink'obj2['age'] 18
}
obj2.age = 20
console.log(obj1.age)//18
// 方式3: 0bject.assign()
const obj2 = {}
Object.assign(obj2,obj1)
obj2.age=20
console.log(obj1.age)//18
例2
// 2.for循环
const obj2 = {}
for (let k in obj1) {
[obj2[k] = obj1[k]
}
obj2.hobby.push("敲代码’)
console.log(obj1)
const obj1 = {
uname:李雷,
age: 18,
hobby: ['唱歌','跳舞 '],
wife: {
uname: '韩梅梅',
age: 16
}
}
// 1.扩展运算符
// 2.for循环
const obj2 = {}
for (let k in obj1) {
[obj2[k] = obj1[k]
}
obj2.hobby.push("敲代码')
console.log(obj1)//也增加了敲代码
// 3.0bject.assign()
转为字符串
深拷贝: JSON.stringify() JSON.parse()
const obj1 = {
uname:"李雷
age: 18,
hobby: ['唱歌','跳舞' ],
wife:{
uname :'韩梅梅',
age: 16
}
}
const str = JSON.stringify(obj1)
const obj2 = JSON.parse(str) // obj2就是使用str生成的全新的一个对象 所有层都是新的
const obj3 = JSON.parse(str)
obj2.hobby.push("敲代码')
obj2.wife.age = 21
console.log(obj1)
console.log(obj2)
// 使用JSON.stringify和JSON.parse完成对obj1的深拷贝
打印结果
节流封装
function fn(cb) {
let flag = true
return function(){
if (flag){
flag = false
setTimeout(function(){flag=true}, 3000)
cb()
}
}
}
实现深拷贝的3种方式: 1JSON 2.lodash 3.自己实现深拷贝
使用lodash完成对obj1的深拷贝
// 数字,字母,下划线,$
const obj1 = {
uname:'李雷',
age: 18,
hobby: ["唱歌","跳舞'],
wife: {
uname :'韩梅梅',
age: 16
}
}
// 使用lodash完成对obj1的深拷贝
const obj2 = _.cloneDeep(obj1)
obj2.hobby.push(敲代码')
obj2.wife.age = 21
console.log(obj1)
自己实现深拷贝
数据 instanceof 构造函数
const num = 10
const str = 'haha'
const arr = [1, 2, 3]
const obj = { // const obj = new Object()
uname : 'zs'
}
//数据 instanceof 构造函数
console.log( arr instanceof Array)//true
console.log( obj instanceof Object)//true
console.log( arr instanceof object)//true
数组也是一种对象
console.log( arr instanceof object)//true
for (let k in obj1) {
//如果 obi2[k] 不是基本数据类型的时候,我们希望有新的数组或者对象 被创建出来
// obj2[k] = obj1[k] // obj2[ 'hobby'] = obj1[ 'hobby']
//obj2['wife'] = obj2[ 'wife']
if (obj1[k] instanceof Array) {
obj2[k] = [...obj1[k]]//填充数组
}else if (obj1[k] instanceof Object) {
obj2[k] = {
...obj1[k]
}
// 填充对象中的东西
}else {
obj2[k] = obj1[k]
}
}
console.log(obj2)
递归
递归: 1.函数调用的方式,在函数自身调用自身
2.在调用自己之前会进行条件判断 不调用自己的情况就是出口
function getSum(num) {
if (num === 1) {
return 1
}
return num + getSum(num-1)
}
// getSum(100)
// 100 + getSum(99)
// 99 + 1~98之间的累加和
// 98 + 1~97之间的累加和
// ........
// 2 + 1~1之间的累加和
console.log(getSum(100)) // 打印出 1到100之间的累加和
使用递归,求出任意一个位置上的斐波那契数列的值
//使用递归,求出任意一个位置上的斐波那契数列的值
// 1 1 2 3 5 8 13 21
function getFiber(num) {
if(num === 1 || num === 2){
return 1
}
return getFiber(num-1) +getFiber(num-2)
}
console.log(getFiber(8)) // 打印出 第8个位置 斐波那契数列的值 21
通过调用deepCopy方法, 让obj1里的东西都完全赋值一份, 放到obj2中
const obj1 = {
uname: '李雷',
age: 18,
hobby: ['唱歌', '跳舞'],
wife: {
uname: '韩梅梅',
age: 16,
car: {
brand: '宝马',
year: 3
}
}
}
const obj2 = {}
function deepCopy(newObj, oldObj) {
}
for (let k in oldobj) {
// 判断 oldobj[k] 是不是一个数组 是不是一个对象
if (oldobj[k] instanceof Array) {
const arr = []
// 把oldobj[k]里的每个东西放到arr中去
deepCopy(arr, oldobj[k])
newObj[k] = arr
} else if (oldobj[k] instanceof Object) {
const obj = {}
// 把 oldobj[k] 里的每个东西放到 obj
deepCopy(obj, oldobj[k])
newobj[k] = obj
} else {
newobj[k] = oldobj[k]
}
}
deepCopy(obj2, obj1) // 通过调用deepCopy方法, 让obj1里的东西都完全赋值一份, 放到obj2中
console.log(obj2)
执行过程
课堂练习15_模拟setlnterval效果
不是递归
getTime不是函数体内部自己调用 是有window在1s钟之后,帮我们调用
并没有递归的出口
//getTime不是函数体内部自己调用 是有window在1s钟之后,帮我们调用
//并没有递归的出口
function getTime() {
document.querySelector( 'div').innerHTML = new Date().toLocaleString()
setTimeout(getTime, 1000)
}
getTime()
//使用setTimeout模拟出setInterval的效果,每隔1s更新div的时间显示
捕获异常
//捕获异常
console.log('1')
const div = document.querySelector('div')
div.addEventListener('click', function(){})
console.log('2')
console.log('3')//2和3会打印吗? 为啥
//不会 捕获异常会影响后面的代码
捕获异常会影响后面的代码 那如何降低影响代码的范围
try catch 可以帮我们捕获错误,让这个错误影响的范围变小
console.log('1')
const div = document.querySelector( 'div')
try {
div.addEventListener('click',function(){})
}catch {
console.log('出现错了啦.....')
}
console.log('2')
console.log('3')
// 2和3会打印吗? 为啥
//会打印 try catch 可以帮我们捕获错误,让这个错误影响的范围变小
//如何防止参数不传?
function fn(num1, num2) {
if (!num1 !num2) {
//我要制造错误,让调用者付出代价
throw new Error( '叫你不传递参数')
}
return num1 + num2
}
//我封装函数的目的: 给别人调用
console.log( 'aa')
console.log( 'bb')
fn()
console.log('111')
console.log('222')
打断点的方式
函数调用的方式1.函数名() 2.函数名.
console.dir(fn)//打印函数
其实函数 ,也是一个对象, 它也可以点出这个函数对象中有具备的一些东西
立马调用
函数名.call
函数名.apply
不会立马调用
函数名.bind
改变函数中的this指向 ,fn.call(obj2) -->this:obj2
函数名.call
函数名.apply
只能传递两个参数
课堂练习19 使用call改变函数内部this指向
函数.bind()
不会立马调用
<button>点我</button>
<script>
const btn = document.querySelector('button')
btn.addEventListener('click', function(){
const fn = function(){
this.style.backgroundColor = 'green'
}
setTimeout(fn.bind(btn), 3000)
// 函数.call()
// 函数.apply()
// 函数.bind(btn) //产生了一个新的函数,这个新函数内部的this就是 btn
})
</script>
改变this指向
改变this指向
节流
防抖:只让最后一次生效
节流只是把一段代码的频率降低了而已
防抖: 只让最后一次生效
防抖的口诀: 将原本要做的事情,晚一点点执行
开启新定时器之前,取消之前的定时器
//监听input的输入,实时判断输入的是不是一个手机号,如果不是,则在span中提示: 输入有误.如果是,span不显示任何东西
// 把原本要做的事情:稍稍的晚那么一点点执行.因为晚了一点点时间执行,我们就有可能取消这次操作
let timerId = null
// input: focus blur keydown keyup input change
document.querySelector( 'input').addEventListener('input', function(){
clearTimeout(timerId)
timerId = setTimeout(function(){
// 判断 this.value /1[3456789]\d[9}/
const reg = /1[3456789]\d{9}/
if (reg.test(this.value)) {
document.querySelector( ' span').innerHTML = ''
} else {
document.querySelector('span').innerHTML = '输入有误'
}
},300)
})
// 节流只是把一段代码的频率降低了而已
// 防抖: 只让最后一次生效