函数this的三种指向
01函数this的三种指向
1.环境对象 this : 谁调用我,this指向谁
this相当于中文中的'我'
2.this指向取决于函数的调用,函数调用有三种方式
(1) 普通函数; 函数名() this->window
(2)对象方法: 对象名.方法名 this->对象
(3) 构造函数; new 函数名() this-> new创建的实例对象
一句话总结,this指向三选一 没点没new是window, 有new是实例,有点是点左边的对象
<script>
/*
1.环境对象 this : 谁调用我,this指向谁
this相当于中文中的'我'
2.this指向取决于函数的调用,函数调用有三种方式
普通函数; 函数名() this-window
对象方法: 对象名.方法名 this-对象
构造函数; new 函数名() this-new创建的实例对象
一句话总结,this指向三选一
*/
function fn(){
console.log(this);
}
// (1)普通函数
fn() //this -window
//
// (2)构造函数
new fn() //this -new创建的实例对象
let obj={
name:'张三',
eat:fn
}
// (3)对象方法
obj.eat() //this-对象
</script>
02this指向测试
<script>
/*
环境对象 this :
普通函数;
对象方法:
构造函数;
*/
//作用域链
let obj = {
name: "张三",
eat: function() {
console.log(this)
function fn() {
console.log(this)
}
fn()
}
}
let eat = obj.eat
obj.eat()
</script>
02函数的上下文调用
01-call()调用函数
上下文调用 : 修改函数内部的this
1 函数名.call ({修改后的this指向},形参1,形参2…………)
<script>
/*
1.环境对象 this : 谁调用指向谁
普通函数;函数名() this-window
对象方法: 对象名.方法名() this-对象
构造函数; new 函数名() this-new构造的实例对象
*** 默认情况下,函数内的this是固定的,无法被修改
*** 如果想要动态修改函数内部的this指向,啧需要使用上下文调用方法
* 上下文调用 :函数作用域 上下文指向 修改函数作用域内部this指向
2.上下文调用 :
2.1 函数名.call() call(修改的this,参数1,参数2)
2.2 函数名.apply()
2.3 函数名.bind()
3. 面试必问: call 和 apply 和 bind三者区别
*/
function fn(a,b){
console.log(this);
console.log(a+b);
}
// 函数名.call(修改的this,参数1,参数2)
fn.call({name:'张三'},10,20)
</script>
call场景-伪数组转真数组
//值类型
// 字符串 数字 布尔 undefined null
//引用类型
// 数组 函数 对象
函数名.call() 应用:万能数据类型检测
1.typeof 数据:检测数据类型,但是有两种数据类型无法检测
*typeof无法检测 数组和null两种数据类型得到的都是 object
2.万能数据类型检测 :Object.prototype.toString.call(数据)
加call的原因是 Object.prototype.toString 检测的是它自身的数据类型
<script>
/* 函数名.call() 应用:万能数据类型检测 */
/*
1.typeof 数据:检测数据类型,但是有两种数据类型无法检测
*typeof无法检测 数组和null两种数据类型得到的都是 object
2.万能数据类型检测 :Object.prototype.toString.call(数据)
加call的原因是 Object.prototype.toString 检测的是它自身的数据类型
*/
//值类型
// 字符串 数字 布尔 undefined null
let str='abc'
let num=123
let bol=true
let und=undefined
let nul=null
//引用类型
// 数组 函数 对象
let arr=[1,2,3]
let fn=function(){}
let boj={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(unl)); // [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]
// (1)Object.prototype.toString()内部会返回this的固定类型,得到固定格式字符串‘[object 数据类型 ]’
// (2)使用object原型中的toString()要想得到数据类型,只需要把this修改成你想要检测的对象
</script>
apply()调用函数
函数名.apply() apply(修改的this,数组/伪数组)
apply会自动帮你遍历数组,然后按照顺序逐一传参
<script>
function fn(a,b){
console.log(this);
console.log(a+b);
}
// (1) 函数名.call(修改的this,参数1,参数2)
fn.call({name:'张三'},10,20)
// (2)函数名.apply(修改的this,数组/伪数组)
// apply会自动遍历数组和伪数组 ,然后逐一传参
fn.apply({name:'李四'},[50,60])
</script>
apply场景 :伪数组转数组
apply()场景 :伪数组转真数组
伪数组: 有 数组三要素(下标,元素 ,长度),不能使用数组的方法
*伪数组的本质是 对象
<script>
/*
apply()场景 :伪数组转真数组
伪数组: 有 数组三要素(下标,元素 ,长度),不能使用数组的方法
*伪数组的本质是 对象
*/
let obj={
0:10,
1:20,
2:30
}
console.log(obj);
// 需求 有时候伪数组 想要使用真数组的方法 就需要把伪数组转换成真数组
// (1)把伪数组的元素取出,push到真数组中
let arr=[]
// arr.push(obj[0],obj[1],obj[2])
// console.log(arr)
// (2)手动写循环遍历添加
// for (let i = 0; i < obj.length; i++) {
// arr.push(obj[i])
// }
// (3)arr.push.apply(arr,伪数组)
// 这里使用apply不是为了修改this ,而是借助apply的传参特点: 自动遍历伪数组/数组传参 所以第一个参数应该写arr(保持this不变)
arr.push.apply(arr,obj)
console.log(arr);
// ES6 伪数组转真数组,固定静态方法 Array.from(伪数组)
let newArr=Array.from(obj)
console.log( newArr);
</script>
ES6 语法
伪数组转真数组,固定静态方法 Array.from(伪数组)
let newArr=Array.from(obj)
console.log( newArr);
apply场景02求数组最大值
apply()场景 :求数组最大值
<script>
/*
apply()场景 :求数组最大值
*/
let arr=[4,845,65,8,99,24,3]
// (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高级 Math.max
let max1=Math.max.apply(Math,arr)
console.log(max1);
// ES6 :Math.max(...arr)
// 功能类似于apply,也会自动的把数组给遍历
let max2=Math.max(...arr)
console.log(max2);
</script>
bind()调用函数
1.环境对象 this : 谁调用指向谁
普通函数;函数名() this-window
对象方法: 对象名.方法名() this-对象
构造函数; new 函数名() this-new构造的实例对象
*** 默认情况下,函数内的this是固定的,无法被修改
*** 如果想要动态修改函数内部的this指向,则需要使用上下文调用方法
* 上下文调用 :函数作用域 上下文指向 修改函数作用域内部this指向
2.上下文调用 :
2.1 函数名.call() call(修改的this,参数1,参数2)
2.2 函数名.apply() apply(修改的this,数组/伪数组)
2.3 函数名.bind() 函数名.bind(修改的this)
*bind 不会立即执行函数,而是得到修改的this的新函数
*bind() 一般修改:定时器函数 ,事件处理函数
<script>
function fn(a,b){
console.log(this);
console.log(a+b);
}
// (1) 函数名.call(修改的this,参数1,参数2)
fn.call({name:'张三'},10,20)
// (2)函数名.apply(修改的this,数组/伪数组)
// apply会自动遍历数组和伪数组 ,然后逐一传参
fn.apply({name:'李四'},[50,60])
//(3)函数名.bind(修改的this)
//bind 不会立即执行函数,而是得到一个修改的this之后的新函数
let newFn=fn.bind({name:'王五'})
newFn(22,33)
</script>
bind场景-修改定时器this
/* bind()场景: 修改定时器的this */
// 定时器中的this默认指向window,如果要修改定时器的this,就需要使用this
<script>
/* bind()场景: 修改定时器的this */
// 定时器中的this默认指向window,如果要修改定时器的this,就需要使用this
let fn= function (){
console.log(this);
}
let newFn=fn.bind({name:'1111'})
setTimeout(newFn,2000)
// 上面代码可以简写一行
// setTimeout(function(){}.bind(),2000)
setTimeout(function(){
console.log(this);
}.bind({name:'李四'}),2000)
/*
变量:是内存空间 只有存储功能,没有运算功能
变量只有两种语法 :存 ,取
字面量 :是数据 .只有运算功能,没有存储功能
*/
// let arr=[10,20,30]
// [10,20,30][0]
</script>
经典面试题 call apply bind区别
3. 面试必问: call 和 apply 和 bind三者区别
相同点 都可以修改this指向
不同点
(1)传参方式不同 call是单个传参,apply是数组/伪数组传参
(2)执行机制不同 call和apply会立即执行函数,bind不会立即执行而是得到修改this的新函数
03闭包
1.闭包closure是什么 : 两个条件 a:函数 b:函数内部还要访问 其他函数的变量
其他函数的变量不能是全局变量,也不能是自己的变量,必须是其他函数的
(1)闭包是一个 访问其他函数内部变量 的函数
(2) 闭包 =函数 + 上下文引用
2.闭包作用 : 解决变量污染
<script>
//num +fn1()组成了闭包
let age=20
function fn(){
let num=20
function fn1(){
console.log(num);
console.log(age);
}
fn1()
}
fn()
</script>
闭包案例
<script>
// 打事件断点 从函数体开始 点击事件类型
// 点击
document.querySelector('.btn').addEventListener('click',function(){
// (1)获取输入框文本
let text=document.querySelector('input').value
// (2)模拟网络请求
setTimeout(function(){
alert(`${text}的搜索结果为123456`)
},1000)
})
</script>
04递归
1.递归函数: 一个函数 在内部 调用自己
* 递归作用和循环类似的,也需要有结束条件
1.递归函数: 在函数中调用自己
*递归类似于循环,也要有结束条件
<script>
/*
1.递归函数: 在函数中调用自己
*递归类似于循环,也要有结束条件
2.递归应用:
*/
function fn(){
console.log('hh');
// 递归调用
fn()
}
// 双函数递归
function fn1(){
console.log('嘿嘿');
fn2()
}
function fn2(){
console.log('呵呵');
fn1()
}
</script>
02浅拷贝与深拷贝json实现
1. 浅拷贝与深拷贝 有两种实现方式
浅拷贝:拷贝地址,修改拷贝后的数据对原数据 有影响
深拷贝 :拷贝数据,修改拷贝后的数据对原数据 没有影响
2.深拷贝两种方式
(1) json方式 let newObj=JSON.parse(JSON.stringify(js对象))
*/
浅拷贝
浅拷贝 :拷贝地址
浅拷贝如果是一层对象,不互相影响,可以直接拷贝值
如果是复杂类型的拷贝 ,出现多层拷贝会相互影响
<script>
let obj = {
name:'张三',
age:20,
sex:'男',
hobby:['吃饭','睡觉','学习']
}
let newObj=obj
newObj.name='李四'
console.log(obj.newObj);
</script>
深拷贝
深拷贝拷贝的是对象,不是地址
常见方法
1,通过递归实现深拷贝
2.lodash/cloneDeep
3.通过JSON.stringfiy()实现
js库loadsh里面cloneDeep内实现了深拷贝
json深拷贝
// json 深拷贝
// (1)JSON.stringify(js对象):把js对象 ->json字符串(json 底层会自动深拷贝)
<script>
let obj = {
name:'张三',
age:20,
sex:'男',
hobby:['吃饭','睡觉','学习']
}
// let json =JSON.stringify(obj)
// (2) JSON.parse(json字符串) :json字符串 ->js对象
// let newObj=JSON.parse(json)
// 简写一行
let newObj=JSON.parse(JSON.stringify(obj))
newObj.name='李四'
newObj.hobby[0]='游戏'
console.log(obj,newObj);
</script>
03浅拷贝与深拷贝 递归实现
2.递归应用:
浅拷贝与深拷贝 :
json方式 let newObj=JSON.parse(JSON.stringify(js对象))
遍历dom树
如果是数据,直接进行拷贝 如果是数组或者对象 要 (1)声明一个空数组 (2)递归遍历旧的数组,把旧的数组里的数据追加给新的数组
(两者数组和对象的地址一样,所以修改新的数据的同时修改了旧的数据)
<script>
/*
1.递归函数:
2.递归应用:
浅拷贝与深拷贝 :
json方式 let newObj=JSON.parse(JSON.stringify(js对象))
遍历dom树
*/
let obj = {
name:'张三',
age:20,
sex:'男',
hobby:['吃饭','睡觉','学习'],
student:{
name:"班长",
score:90
}
}
// 深拷贝函数封装
function kaobei(obj,newObj){
// 遍历obj ,把里面的数据拷贝给newObj
for(let key in obj){
// 判断是不是数组,如果是数组还需要继续遍历拷贝
if(obj[key] instanceof Array){
// (1)声明一个空数组
newObj[key]=[]
// (2)递归遍历数组
kaobei(obj[key],newObj[key])
}else if(obj[key] instanceof Object){
// (1)声明一个空数组
newObj[key]=[]
// (2)递归遍历数组
kaobei(obj[key],newObj[key])
}else{
newObj[key]=obj[key]
}
}
}
</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: ["雀巢咖啡"]
}
]
}
]
// 封装一个添加菜单的函数
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>