01-函数的三种调用方式(this指向)==
1.复习函数三种调用方式:普通函数 对象方法 构造函数
- 重点:理解this关键字作用:
谁调用这个函数,this指向谁
函数三种执行模式 : 全局函数 、 对象方法 、 构造函数
this : 谁 调用 我,我就指向谁
-
全局函数 : this指向window
-
对象方法 : this指向对象
-
构造函数 : this指向new创建的空对象
小技巧: (1)没有点.没new就是widow
(2)有点就是左边的对象
(3)有new就是实例对象
//声明函数
function fn() {
console.log(this)
}
//1.普通函数
fn()//window
//2.对象的方法
let obj = {
name: '张三',
age: 20,
eat: fn
}
obj.eat()//对象
//3.构造函数
new fn()//new 创建的实例对象
02-函数调用的上下文模式==
2.函数三种调用方式有一个共同点: this指向无法修改
3.函数上下文调用 : 用于修改函数中的this指向
3.1 函数名.call(修改this,形参1,形参2...)
3.2 函数名.apply(修改this,数组或伪数组)
3.3 函数名.bind(修改this)
不会立即执行函数,而是得到一个修改this后的新函数
bind一般用于修改,定时器函数\事件处理函数
- call和apply和bind区别:传参方式不同
function fn(a, b) {
console.log(this)
console.log(a + b)
}
//普通函数 调用函数
fn(10, 20)
//上下文调用 : 函数名.call(修改this,形参1,形参2...)
fn.call({ name: '张三' }, 30, 40)
call应用场景
1. typeof 数据 : 检测数据类型
* 有两种数据类型无法检测 : null 和 array
2. 万能数据类型检测 : Object.prototype.toString.call( 数据 )
*/
//值类型(基本数据类型)
let str = 'abc'
let num = 123
let bol = true
let und = undefined
let nul = null
//引用类型(复杂数据类型)
let arr = [10, 20, 30]
let fn = function () { }
let obj = { name: '张三' }
console.log(Object.prototype.toString.call(str))//'[object String]'
console.log(Object.prototype.toString.call(num))//'[object Number]'
console.log(Object.prototype.toString.call(bol))//'[object Boolean]'
console.log(Object.prototype.toString.call(und))//'[object Undefined]'
console.log(Object.prototype.toString.call(nul))//'[object Null]'
console.log(Object.prototype.toString.call(arr))//'[object Array]'
console.log(Object.prototype.toString.call(fn))//'[object Function]'
console.log(Object.prototype.toString.call(obj))//'[object Object]'
apply调用
function fn(a,b,c){
console.log( this )
console.log(a+b)
}
//普通函数 调用函数
fn(10,20,30)
//(1) 函数名.call(修改this,形参1,形参2...)
fn.call({name:'张三'},30,40,50)
//(2) 函数名.apply(修改this,数组/伪数组)
// apply自动遍历数组/伪数组, 然后按照元素顺序逐一传参
fn.apply({name:'李四'},[30,40,50] )
apply应用场景(求最大值)
//求数组最大值
let arr = [80,100,99,20,50]
//1.js基础:擂台思想 (忘掉舍弃)
let max = arr[0]
for(let i = 1;i < arr.length;i++){
if( arr[i] > max ){
max = arr[i]
}
}
console.log(max)
//2.js高级写法
let max1 = Math.max.apply(Math,arr)
console.log( max1 )
//ES6 (常用)
// ...是ES6新增的一种运算符,类似于apply也会自动遍历arr,然后逐一传参
let max2 = Math.max(...arr)
console.log(max2)
bind应用场景(定时器 事件处理函数)
4. call和apply和bind区别
4.1 传参方式不同 : call是逐一传参, apply是数组/伪数组 传参方式不同
4.2 执行机制不同 : call和apply立即执行函数, bind不会立即执行函数
* call和apply用一次,改一次
* bind修改一次,终生有效
*/
function fn(a, b, c) {
console.log(this)
console.log(a + b)
}
//普通函数 调用函数
fn(10, 20, 30)
//(1) 函数名.call(修改this,形参1,形参2...)
fn.call({ name: '张三' }, 30, 40, 50)
//(2) 函数名.apply(修改this,数组/伪数组)
// apply自动遍历数组/伪数组, 然后按照元素顺序逐一传参
fn.apply({ name: '李四' }, [30, 40, 50])
//(3) 函数名.bind(修改this)
// bind不会立即调用函数,而是得到一个修改this之后的新函数
// bind一般用于修改不会立即执行的函数中的this : 定时器、事件处理函数
let fn1 = fn.bind({ name: '王五' })
fn1(6, 7)
//定时器中的this默认指向window
setTimeout(function () {
console.log(this)
}.bind({ name: '王五' }), 3000)
03-闭包(closure)
1.闭包 :
a.闭包是一个访问其他函数内部变量的函数 (函数 + 其他函数内部变量)
b.闭包 = 函数 + 组合(上下文引用),指的是局部作用域
2.判断闭包 :
代码角度两个条件: 函数 + 上下文引用(使用其他函数局部变量)
控制台角度 : 会出现 closure提示
3.闭包作用 : 解决全局变量污染,一般用于回调函数.
4.在浏览器中调试闭包
04-递归
1.1-递归函数介绍
本小节知识点
- 1.递归函数:一个函数在内部自己调用自己
- 循环能用的就用循环,不能用的再用递归,使用场景不多
- 2.递归函数特点
-
- a.一定要有结束条件,否则会导致死循环
- b.能用递归函数实现的需求,就一定可以用循环调用函数来解决,只是代码简洁与性能不同而已
function fn(){
console.log('今天学得很开心')
fn()
}
// fn()
//双函数递归 : 两个函数互相调用
function fn1(){
console.log('哈哈')
fn2()
}
function fn2(){
console.log('呵呵')
fn1()
}
// fn1()
1.2-递归应用场景:浅拷贝与深拷贝:json实现
递归应用:
浅拷贝与深拷贝 :
方式一(推荐) : JSON方式实现
- let newObj = JSON.parse( JSON.stringify( obj ) )
方式二(递归) : 了解
遍历dom树
*/
let obj = {
name:'张三',
age:20,
sex:'男',
hobby:['吃饭','睡觉','学习']
}
//浅拷贝: 拷贝地址
// let newObj = obj
// 修改拷贝后的数据,原数据也会修改
// newObj.name = '李四'
// console.log( obj,newObj)
//深拷贝 : 拷贝数据
//(1)先把js对象 -> JSON字符串 (JSON会自动帮你深拷贝)
// let jsonStr = JSON.stringify( obj )
//(2)再把 JSON字符串 -> js对象
// let newObj = JSON.parse( jsonStr )
let newObj = JSON.parse( JSON.stringify( obj ) )
newObj.name = '李四'
console.log(obj,newObj)
1.3-递归应用场景: 浅拷贝与深拷贝:递归实现
let obj = {
name:'张三',
age:20,
sex:'男',
hobby:['吃饭','睡觉','学习'],
student:{
name:"班长",
score:90
}
}
//使用递归函数
function kaobei(obj,newObj){
for(let key in obj){
if( obj[key] instanceof Array ){
//声明一个空数组,然后继续拷贝数组里面的数据
newObj[key] = []
//递归调用继续拷贝 数组
kaobei(obj[key],newObj[key])
}else if( obj[key] instanceof Object ){
//声明一个空对象
newObj[key] = {}
//递归调用继续拷贝 对象
kaobei(obj[key],newObj[key])
}else{
newObj[key] = obj[key]
}
}
}
//创建一个空对象,然后深拷贝
let newObj = {}
kaobei(obj,newObj)
newObj.name = '李四'
newObj.hobby[0] = '摸鱼'
newObj.student.name = 'ikun'
console.log( obj,newObj)
1.4-递归应用场景:遍历dom树
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
<style>
* {
padding: 0;
margin: 0;
}
.menu p {
width: 100px;
border: 3px solid;
margin: 5px;
}
.menu > div p {
margin-left: 10px;
border-color: red;
}
.menu > div > div p {
margin-left: 20px;
border-color: green;
}
.menu > div > div > div p {
margin-left: 30px;
border-color: yellow;
}
</style>
</head>
<body>
<div class="menu">
<!-- <div>
<p>第一级菜单</p>
<div>
<p>第二级菜单</p>
<div>
<p>第三级菜单</p>
</div>
</div>
</div> -->
</div>
<script>
//服务器返回一个不确定的数据结构,涉及到多重数组嵌套
let arr = [
{
type: "电子产品",
data: [
{
type: "手机",
data: ["iPhone手机", "小米手机", "华为手机"]
},
{
type: "平板",
data: ["iPad", "平板小米", "平板华为"]
},
{
type: "智能手表",
data: []
}
]
},
{
type: "生活家居",
data: [
{
type: "沙发",
data: ["真皮沙发", "布沙发"]
},
{
type: "椅子",
data: ["餐椅", "电脑椅", "办公椅", "休闲椅"]
},
{
type: "桌子",
data: ["办公桌"]
}
]
},
{
type: "零食",
data: [
{
type: "水果",
data: []
},
{
type: "咖啡",
data: ["雀巢咖啡"]
}
]
}
]
//封装一个遍历dom树函数
function addElement(arr, father) {
//遍历数组
for (let i = 0; i < arr.length; i++) {
//(1)创建空标签
let div = document.createElement("div")
//(2)设置内容
div.innerHTML = `<p>${arr[i].type || arr[i] }</p>`
//(3)添加到父盒子
father.appendChild(div)
//如果元素还有data属性,则需要使用递归继续添加下级菜单
if( arr[i].data ){
addElement(arr[i].data , div)
}
}
}
addElement(arr, document.querySelector(".menu"))
</script>
</body>
</html>
05-递归小节
1.递归
- 什么是递归:函数内部调用自己
- 递归场景
-
- 深拷贝
- 遍历dom树
2.this三种指向
this : 谁 调用 我,我就指向谁 1.全局函数 : this指向window
2.对象方法 : this指向对象
3.构造函数 : this指向new创建的空对象
3.call、apply、bind区别
- 相同点:都是修改函数this指向
- 不同点
-
- 传参方式不同: call用于单个参数,apply用于多个参数
- 执行机制不同: call与apply会立即执行, bind不会立即执行
-
- call、apply用一次修改一次
- bind;一次修改,终生有效
4.闭包
- 什么是闭包:以下两种回答都可以
-
- 闭包是一个访问其他函数内部变量的函数
- 闭包是 函数 + 上下文代码组合
- 闭包作用:解决变量污染