this三种指向
this环境对象: 谁"调用"我.我就指向谁
重点: 与函数声明无关,取决于函数调用
(1)全局函数: 函数名()
this指向window
(2)对象方法: 对象名.方法名()
this指向对象
(3)构造函数:new 函数名()
this指向new创建的实例对象
<script>
//1.全局函数
function fn(){
console.log('111111')
console.log(this)
}
fn();//window.fn()
//2.对象的方法
let obj = {
name:'班长',
sayHi:function(){
console.log('我是胖胖又可爱的得小又又')
console.log(this)
}
}
obj.sayHi()
//将fn的地址赋值给sayHi
obj.sayHi = fn
//此时this指向obj,this指向跟声明没有关系。取决于函数是如何调用的
obj.sayHi()
//3.构造函数
function Person(name,age){
//(1)创建一个空对象 (2)this指向这个对象 (3)执行赋值代码 (4)返回这个对象
//this :指向new创建的哪个对象
console.log(this)
this.name = name
this.age = age
}
let p1 = new Person() // 构造函数
//没有加new,以全局函数方式执行。此时this就是window,函数里面其实是给window添加属性(全局变量)
Person('张三',18) // 全局函数
console.log(name)
console.log(age)
</script>
函数上下文调用
1.函数上下文调用: 修改函数中this的指向
(1)函数名.call(修改的this的指向,参数1,参数2...)
call应用场景: 数据类型检测
(2)函数名.apply(修改的this的指向,数组/伪数组)
apply应用场景: 伪数组转成真数组, 求数组最大值
(3)函数名.bind(修改this的指向)
bind不会立即调用函数,而是得到一个修改this之后的新函数
细节: 如果你在bind后面传了函数参数,那么参数也会绑定。之后传参无效
bind应用场景: 主要是修改'不需要立即执行的函数',事件处理函数,定时器函数
2. call, apply, bind的异同点
相同点: 都是用来修改this指向
不同点:
传参方式不同: call是一一传参; apply是数组/伪数组,自动遍历后逐一传参
执行机制不同: call和apply会立即调用函数; bind不会立即调用,而是得到一个修改this之后的新函数
call上下文调用
<script>
function fn(a, b) {
console.log(this)
console.log(a + b)
}
fn(1, 2) // this指向window
// 函数名.call(修改的this的指向,参数1,参数2...)
fn.call({name: '张三'}, 1, 2)
</script>
call应用场景
数据类型检测
<script>
/* 需求: 数据类型检测 */
/*
typeof 数据: 检测数据类型
typeof无法检测两种数据类型:null,array 都会得到object
*/
console.log(typeof 123) // number
console.log(typeof '123') // string
console.log(typeof true) // boolean
console.log(typeof undefined) // undefined
console.log(typeof null) // object
console.log(typeof [1, 2, 3]) // object
console.log(typeof function () {}) // function
console.log(typeof {name: '张三'}) // object
/*
Object.prototype.toString(): 得到固定格式字符串
[object Type], 其中Type是对象的类型
检测数据类型固定格式语法: Object.prototype.toString.call(数据)
*/
console.log(Object.prototype.toString.call(123)) // [object Number]
console.log(Object.prototype.toString.call('123')) // [object String]
console.log(Object.prototype.toString.call(true)) // [object Boolean]
console.log(Object.prototype.toString.call(undefined)) // [object Undefined]
console.log(Object.prototype.toString.call(null)) // [object Null]
console.log(Object.prototype.toString.call([1, 2, 3])) // [object Array]
console.log(Object.prototype.toString.call(function () {})) // [object Function]
console.log(Object.prototype.toString.call({name: '张三'})) // [object Object]
</script>
apply上下文调用
<script>
function fn(a, b) {
console.log(this)
console.log(a + b)
}
fn(1, 2) // this指向window
// 函数名.call(修改的this,参数1,参数2...)
fn.call({name: '张三'}, 1, 2)
// 函数名.apply(修改的this的指向,数组/伪数组)
// apply传参的时候会自动的遍历这个数组,然后按照顺序逐一传参
fn.apply({name: '李四'}, [3, 4])
</script>
apply应用场景
伪数组转成真数组
<script>
let obj = {
0: 20,
1: 50,
2: 88,
3: 33,
length: 4
}
console.log(obj)
// 需求: 伪数组转成真数组
// 1. ES5: apply上下文调用
let newArr = []
/*
函数名.apply(修改的this, 数组/伪数组)
第一个参数: newArr 本来this就是newArr,这里面不需要修改this
第二个参数:obj 借助apply的特点:自动遍历伪数组/数组,逐一传参(省去for循环)
*/
// newArr.push(obj[0], obj[1], obj[2], obj[3])
newArr.push.apply(newArr, obj)
console.log(newArr)
// 2. ES6(推荐): Array.from()
let arr = Array.from(obj)
console.log(arr)
</script>
求数组最大值
<script>
/*
伪数组: 有数组三要素(下标,元素,长度),但是不能使用数组的方法
伪数组: 本质是对象
伪数组不能使用数组方法: 伪数组的原型指向对象的原型,而不是Array的原型
*/
// 需求: 求数组最大值
let arr = [10, 23, 44, 78, 32, 9]
// 1. 排序法: 从大到小排序,取0下标
arr.sort(function (a, b) {
return b - a
})
console.log(arr[0])
// 2. 擂台法
let max1 = arr[0]
for (let i = 0; i < arr.length; i++) {
if (arr[i] > max1) {
max1 = arr[i]
}
}
console.log(max1)
// 3.ES5: Math.max.apply(Math,数组/伪数组)
// 第一个参数Math: this本来就是Math,这里也不需要修改this(传Math相当于this不变)
// 第二个参数arr: 借助apply特点: 自动遍历数组/伪数组,逐一传参
let max2 = Math.max.apply(Math, arr)
console.log(max2)
// 4.ES6(推荐):
let max3 = Math.max(...arr)
console.log(max3)
</script>
bind上下文调用
<script>
function fn(a, b) {
console.log(this)
console.log(a + b)
}
fn(1, 2) // this指向window
// 函数名.call(修改的this,参数1,参数2...)
fn.call({name: '张三'}, 1, 2)
// 函数名.apply(修改的this,数组/伪数组)
// apply传参的时候会自动的遍历这个数组,然后按照顺序逐一传参
fn.apply({name: '李四'}, [3, 4])
// 函数名.bind(修改this)
// bind不会立即调用函数,而是得到一个修改this之后的新函数
let newFn = fn.bind({name: '王五'})
newFn(5,6)
</script>
bind应用场景
修改定时器中的this
<script>
// 定时器中的this一定是window
let fn = function () {
console.log(this)
}
// 使用bind修改this
let newFn = fn.bind({
name: '666'
})
// fn(): 调用函数,运行结果是函数返回值
// fn: 变量取值,取出fn中存储的堆地址
setTimeout(newFn, 3000)
// 下面这个写法和上面写法是一样的: 函数是数据类型,也可以像其他数据一样直接使用用语法
setTimeout(function () {
console.log(this)
}, bind({name: '干饭'}), 3000)
</script>
函数调用上下文模式注意点
1. 函数上下文执行模式 : 动态修改this
注意点 : 修改的this只能是引用类型
2. 如果写的是基本数据类型
string,number,boolean : 自定帮我们转成对应本装包类型 new String() Boolean() Number()
undefined,null :
<script>
function fn() {
console.log(this)
}
fn.call('str')
fn.call(1)
fn.call(true)
//如果传的是undefined和null,或者不传。代码不会报错,也不会帮我们修改this,还是原来的window
fn.call(undefined)
fn.call(null)
fn.call()
fn.call(window)
</script>
闭包
1. 闭包(closure)是什么?
闭包是一个可以访问其他函数内部变量的函数
闭包 = 函数 + 上下文引用(局部作用域的引用)的组合
2. 闭包的作用: 解决变量污染
闭包出现较多的一般都会在回调函数里面
<script>
function fn() {
let a = 1
// 在fn1函数中,访问了其他函数fn内部的变量 fn1+a 形成闭包
function fn1() {
console.log(a)
}
fn1()
}
fn()
</script>
递归函数
1. 什么是递归函数? 函数内部调用自己
注意点: 需要满足条件才会递归,否则会导致死循环
递归函数和循环功能类似
2. 递归函数的应用场景:
(1)浅拷贝与深拷贝
浅拷贝: 拷贝的是地址
,修改拷贝后的数据对原数据有影响
深拷贝: 拷贝的是数据
,修改拷贝后的数据对原数据没有影响
深拷贝实现的两种方式: json转换; 递归函数
(2)遍历dom树
递归应用场景:浅拷贝与深拷贝
<script>
// 声明一个对象
// 拷贝: 把对象中储存的数据拷贝一份赋值给其他对象
let obj = {
name: '张三',
age: 30,
hobby: ['吃饭', '睡觉', '追剧'],
friend: {
name: '李四',
sex: '男'
}
}
//浅拷贝: 拷贝的是地址
let obj1 = obj
//由于浅拷贝,拷贝的是地址。 所以修改拷贝后的数据,原来的数据也会变化
obj1.hobby = '美食'
console.log(obj1, obj)
</script>
浅拷贝转成深拷贝方法一(json转换)
<script>
let obj = {
name: '张三',
age: 30,
hobby: ['吃饭', '睡觉', '追剧'],
friend: {
name: '李四',
sex: '男'
}
}
/*
(1)先把js对象转成json格式字符串: JSON.stringify(js对象)
json在把js转成json格式字符串的时候,底层会自动帮你深拷贝
*/
let json = JSON.stringify(obj)
console.log(json)
/*
(2)再把json字符串转成js对象: JSON.parse(json格式)
*/
/* let js = JSON.parse(json)
console.log(js)
js.hobby = '学习'
console.log(js, obj) */
// 简写
let newObj = JSON.parse(JSON.stringify(obj))
newObj.hobby = '学习'
console.log(newObj, onj)
</script>
浅拷贝转成深拷贝方法二(递归函数)
<script>
let obj = {
name: '张三',
age: 30,
hobby: ['吃饭', '睡觉', '追剧'],
friend: {
name: '李四',
sex: '男'
}
}
/*
(1)遍历obj,把所有的属性添加给newObj
(2)如果obj[key]是引用类型(数组,对象),则不能直接拷贝地址
(2.1)数组:给newObj声明一个空数组,然后遍历obj[key],把里面元素添加给newObj[key]
(2.2)对象:给newObj声明一个空对象,然后遍历obj[key],把里面元素添加给newObj[key]
(3)如果obj[key]不是引用类型,则直接赋值,结束递归
*/
let newObj = {}
function copy(newObj, obj) {
for (let key in obj) {
// 判断 obj[key] 是不是数组类型
if (obj[key] instanceof Array) {
// 声明一个空数组,然后继续拷贝数组里面的数据
newObj[key] = []
copy(newObj[key], obj[key])
} // 判断 obj[key] 是不是数组类型
else if (obj[key] instanceof Object) {
// 声明一个空数组,然后继续拷贝数组里面的数据
newObj[key] = {}
copy(newObj[key], obj[key])
} else {
newObj[key] = obj[key]
}
}
}
// 调用深拷贝函数
copy(newObj, obj)
// 深拷贝: 修改拷贝的数据,对原数据没有影响
newObj.hobby[0] = '111'
newObj.friend.name = '222'
console.log(newObj, obj)
</script>
递归经典场景: 遍历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: ['雀巢咖啡']
}
]
}
]
/* 使用递归遍历数组 */
// arr: 数据 father: 父盒子
function addElement(arr, father) {
for (let i = 0; i < arr.length; i++) {
let div = document.createElement('div')
div.innerHTML = `<p>${arr[i].type || arr[i] }</p>`
father.appendChild(div)
if (arr[i].data) {
addElement(arr[i].data, div)
}
}
}
//调用递归函数
addElement(arr, document.querySelector('.menu'))
</script>
</body>
</html>