1.this指向
- 函数三种执行模式 : 全局函数 、 对象方法 、 构造函数
- this : 谁
调用
我,我就指向谁 (与函数的声明无关 取决于函数的调用)
- 全局函数 :函数名( ) ------------------>this指向window
- 对象方法 :对象名.方法名( ) -------> this指向对象
- 构造函数 :new 函数名( )------------>this指向new创建的空对象
分析this指向心得 : this指向取决于函数调用,而函数有三种调用方式。所以this指向也会有三种情况
(1)优先用排除法分析, 如果有new关键字,则指向new创建的对象。 否则不是window就是对象
(2)如何判断this是window还是对象,就看调用是: 函数名() 还是 对象名.方法名()
let obj = {
name: 'ikun',
sayHi: function () {
function fn() {
console.log(this);
}
fn()// 普通函数调用 :函数名() fn调用了this 所以this指向window
}
}
obj.sayHi()//window
let obj1 = {
name: 'ikun',
sayHi: function () {
console.log(this);
}
}
obj1.sayHi() //对象方法调用 :对象名.方法名() 对象obj1调用了this 所以this指向obj1对象
function Fn() {
console.log(this);
}
new Fn()//构造函数调用 :new 函数名() 根据new执行流程/创建一个空对象-this指向这个对象-对象赋值-返回对象 这里的this指向new创建的空对象
共同的特点: this的指向无法动态修改
2.函数上下文执行模式(call/apply/bind)
2.1上下文模式注意点
- a.函数上下文三个方法:
call()
apply()
bind()
,它们定义在Function构造函数的原型中 - b.如果将this指向修改为值类型:(number,boolean,string),则函数会自动帮我们包装成对应的引用类型(基本包装类型)
- 值类型:
'123'
,1
,true
- 基本包装类型:
String('123')
,Number(1)
,Boolean(true)
- 值类型:
2.2函数上下文作用:
可以动态修改函数中的this指向
2.3 异同点:
- 相同之处:都可以修改函数中this指向
- 不同点:
1.传参方式不同 : call是一一传参,apply是数组/伪数组传参
2.执行机制不同 : call、apply会立即执行函数, bind不会立即执行函数
2.3 语法
有3种写法,作用都是一样改this,应用场景不同
- call()语法:
函数名.call(this修改后的指向,参数1,参数2,...)
--------------适用于函数原本形参 <= 1- apply()语法:
函数名.apply(this修改之后的指向,伪数组或者数组)
-------------适用于函数原本形参 >= 2- bind()语法:
函数名.bind(this修改后的指向)
bind()语法并不会立即执行函数,而是返回一个修改指向后的新函数,常用于回调函数
如果bind的时候传参,则参数也会绑定,之后无法传递实参
<script>
/*
1.函数三种执行方式 :
全局函数 : this指向window
对象方法: this指向对象
构造函数 : this指向new创建的对象
共同的特点: this的指向无法动态修改
2.函数上下文模式 :
2.1 作用: 可以动态修改函数中的this
2.2 语法: 有3种写法,作用都是一样改this,应用场景不同
a. 函数名.call(修改的this,arg1,arg2…………)
* 适用于函数原本形参 <= 1
b. 函数名.apply(修改的this,[数组或伪数组])
* 适用于函数原本形参 >= 2
c. 函数名.bind(修改的this,arg1,arg2…………)
* 特点:不会立即执行这个函数,而是返回修改this后的新函数
* 适用于不会立即执行的函数 : 事件处理函数,定时器函数
*/
// function fn(){
// //三种执行模式this无法动态修改
// //this = {age:18};
// console.log(this);
// };
// fn();//this:window
/* 上下文模式 */
function fn(a, b) {
console.log(this);
console.log(a + b);
};
//a. 函数名.call(修改的this,arg1,arg2…………)
//应用场景: 适用于函数原本形参 <= 1
fn(10, 20);//this:window 30
fn.call({ age: 18 }, 100, 200); //age:18 300
//b. 函数名.apply(修改的this,[数组或伪数组])
//应用场景: 适用于函数原本形参 >=2
fn.apply({ age: 88 }, [20, 30]);
//c. 函数名.bind(修改的this)
//特点:这个函数不会立即执行,而是返回一个修改this之后的新函数
//应用场景 : 事件处理函数,定时器
let newFn = fn.bind({ name: '坤坤' });
console.log(newFn);
newFn(50, 60);
//4. 定时器中的this一定是指向window
// 定时器:一段代码间隔事件执行 setTimeout(一段代码,间隔时间)
//4.1 具名函数
let test = function () {
console.log('我是具名函数');
console.log(this);
};
let newTest = test.bind({ name: '张三' })
setTimeout(newTest, 3000);
//4.2 匿名函数
setTimeout(function () {
console.log('我是定时器中的函数');
console.log(this);
}.bind({ name: '李四' }), 2000);
</script>
函数.call() 应用场景
/*
1. 函数上下问执行模式 : 动态修改this
注意点 : 修改的this只能是引用类型
2.如果写的是基本数据类型
string,number,boolean : 自定帮我们转成对应的基本装包类型 new String() Boolean() Number()
undefined,null :
*/
function fn() {
console.log(this);
};
fn.call('str');//String
fn.call(1);//Number
fn.call(true);//Boolean
//如果传的是undefined和null,或者不传。代码不会报错,也不会帮我们修改this,还是原来的window
fn.call(undefined);//Window
fn.call(null);//Window
fn.call();//Window
fn.call(window);//Window
Object.prototype.toString.call()万能检测数据类型
1.Object.prototype.toString() 得到固定格式字符串
2.检测数据类型固定格式语法:Object.prototype.toString.call()
得到一个固定格式字符串: [object 数据类型]
/* call场景:数据类型检测 */
//1. typeof 数据 : 检测数据类型
/* typeof有两种数据类型无法检测: null,array 都会得到object */
console.log( typeof '123' )// 'string'
console.log( typeof 123 )//'number'
console.log( typeof true )//'boolean'
console.log( typeof undefined )//'undefined'
console.log( typeof null )//'object'
console.log( typeof [10,20,30] )//'object'
console.log( typeof function(){} )//'function'
console.log( typeof {name:'123'} )//'object'
//2. Object.prototype.toString() : 得到固定格式字符串
//"[object type]",其中 type 是对象的类型
//检测数据类型固定格式语法: Object.prototype.toString.call(数据)
console.log( Object.prototype.toString.call('123') )//[object String]
console.log( Object.prototype.toString.call(123) )//[object Number]
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([10,20,30]) )//[object Array]
console.log( Object.prototype.toString.call(function(){}) )//[object Function]
console.log( Object.prototype.toString.call({name:'123'}) )//[object Object]
apply上下文调用
function fn(a,b){
console.log(this)
console.log(a+b)
}
fn(1,2)//普通函数 this->window
//(1)函数名.call(修改的this,参数1,参数2,....)
fn.call({name:'张三'},10,20)
//(2)函数名.apply(修改的this, 数组/伪数组 )
//apply传参的时候会自动的遍历这个数组,然后按照循序逐一传参 a = [30,40][0] b=[30,40][1]
fn.apply({name:'李四'},[30,40])
</script>
arr.push.apply(arr,伪数组)的应用场景:伪数组转真数组
<script>
/*
伪数组: 有数组三要素(下标、元素、长度),但是不能使用数组的方法
伪数组 本质是对象
伪数组不能使用数组方法: 伪数组的原型指向对象的原型,而不是Array的原型
*/
let obj = {
0: 10,
1: 20,
2: 30,
3: 40,
length: 4,//length 省略不了
}
console.log(obj[0]);
//将伪数组转成真数组 arr.push 往数组后面新增元素 返回的是数组的长度
let newArr = []
let a = newArr.push(obj[0], obj[1], obj[2], obj[3])
console.log(newArr)//[10, 20, 30, 40]
console.log(a)//数组长度 4
// ES5:上下文
let newArr1 = []
newArr1.push.apply(newArr1, obj)
//第一个参数:newArr1 本来this就是newArr 这里不需要修改this(相当于this不变)
//第二个参数:obj 借助apply特点:自动遍历伪数组/数组,逐一传参(自己省去了for循环)
console.log(newArr1);
// ES6推荐: Arr.from(伪数组) 伪数组转成真数组
let arr = Array.from(obj)
console.log(arr);
</script>
Math.max.apply(Math, arr)应用场景:求数组最大值
<script>
//求数组最大值
let arr = [10, 13, 12, 3, 14, 23, 32]
// 1.arr.sort() 数组排序 参数:回调函数 固定语法死记硬背
// arr.sort(function (a, b) {
// return a - b//从小到大排
// return b - a//从大到小排序
// })
arr.sort(function (a, b) {
return b - a
})
console.log(arr[0]);
//2.擂台思想
let max = 0
for (let i = 0; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i]
}
}
console.log(max)
// ES5: Math.max.apply(Math, 数组名)
//函数名.apply(修改的this,数组/伪数组)
//第一个参数 Math: Math调用了方法 this指向的就是Math,这里就不需要修改this(传Math,相当于this不变)
//第二个参数 arr :借助apply的特点.自动遍历数组/伪数组 逐一传参
let max3 = Math.max.apply(Math, arr)
console.log(max3);
//ES6(推荐):
let max4 = Math.max(...arr)
console.log(max4)
</script>
bind上下文调用
function fn(a,b){
console.log(this)
console.log(a+b)
}
fn(1,2)//普通函数 this->window
//(1)函数名.call(修改的this,参数1,参数2,....)
fn.call({name:'张三'},10,20)
//(2)函数名.apply(修改的this, 数组/伪数组 )
//apply传参的时候会自动的遍历这个数组,然后按照循序逐一传参 a = [30,40][0] b=[30,40][1]
fn.apply({name:'李四'},[30,40])
//(3)函数名.bind(修改this)
//bind不会立即调用函数,而是得到一个修改this之后的新函数
let newFn = fn.bind({name:'王五'})
newFn(25,66)
bind()应用场景
bind场景主要是修改 '不需要立即执行的函数
//事件处理函数、定时器函数
//bind场景主要是修改 '不需要立即执行的函数'
// 事件处理函数、定时器函数
//1.定时器中的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)
3.递归函数 :
/* 高阶函数: 自调用函数、回调函数、闭包、递归 */
什么是递归函数? : 函数内部调用自己
* 注意点: 需要满足条件才会递归,否则会导致死循环
* 递归函数和循环功能类似
//一个函数递归
// function fn(){
// console.log('哈哈');
// fn();
// };
// fn();
//两个函数递归
// function fn1(){
// console.log('哈哈');
// fn2();
// };
// function fn2(){
// console.log('呵呵');
// fn1();
// };
// fn2();
//需求:写一个函数,打印三次 班长爱坤哥
let i = 1;
function fn(){
console.log('好好学习');
i++;
if(i <= 3){
fn();
};
//循环实现
// for(let i = 1;i<=3;i++){
// console.log('班长爱坤哥');
// };
};
fn();
递归应用场景:
2.1 浅拷贝与深拷贝
1.浅拷贝: 拷贝的是地址, 修改拷贝后的数据对原数据有影响
//声明一个对象
//拷贝: 把对象中存储的数据 拷贝一份赋值给其他对象
let obj = {
name:'ikun',
age:30,
hobby:['讲课','学生','学习'],
friend:{
name:'朋友',
sex:'男'
}
}
//浅拷贝: 拷贝的是地址
let obj1 = obj
//由于浅拷贝,拷贝的是地址。 所以修改拷贝后的数据,原来的数据也会变化
obj1.hobby = '美食'
console.log(obj1,obj)
2.深拷贝:拷贝的是数据, 修改拷贝后的数据对原数据没有影响
let obj = {
name:'ikun',
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]不是引用类型,则直接赋值。结束递归
*/
function kaobei(newObj,obj){
for(let key in obj){
//判断 obj[key] 是不是数组类型
if( obj[key] instanceof Array ){
//声明一个空数组,然后继续拷贝数组里面的数据
newObj[key] = []
kaobei(newObj[key],obj[key])
}else if(obj[key] instanceof Object){
//声明一个空对象,然后继续拷贝数组里面的数据
newObj[key] = {}
kaobei(newObj[key],obj[key])
}else{
newObj[key] = obj[key]
}
}
}
//调用深拷贝函数
let newObj = {}
kaobei(newObj,obj)
//深拷贝:修改拷贝的数据,对原数据没有影响
newObj.hobby[0] = '111'
console.log( newObj,obj)
3.用JSON转换实现深拷贝
/* 2.深拷贝有两种实现方式
2.1 推荐使用:json转换
2.2 递归 :
*/
let obj = {
name:'ikun',
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 )
// js.hobby = '学习'
// console.log( js,obj )
//以上两个流程,可以简写成一行代码
let newObj = JSON.parse( JSON.stringify( obj ) )
newObj.friend = '小狗'
console.log(newObj,obj)
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: ['雀巢咖啡']
}
]
}
]
//arr:数据 father:父盒子
function addElement(arr,father){
//遍历数组,生成div>p添加到父元素中
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)
}
}
}
let menu = document.querySelector('.menu')
//调用函数
addElement(arr,menu)
</script>
</body>
</html>
4.闭包:
- 1.闭包是什么 : 闭包是一个可以访问其他函数内部变量的函数
- 2.闭包的作用 : 解决变量污染
function fn(){
let a = 1
//在fn1函数中, 访问了其他函数fn内部的变量。 fn1 + a 形成闭包
function fn1(){
console.log(a)
}
fn1()
}
fn()
function test(){
let num = 1
setTimeout( function(){
console.log(num)
},2000)
}
test()
闭包场景:
<script>
/*
1.闭包(closure)是什么?
1.1 闭包是一个访问其他函数内部变量的函数
1.2 闭包 = 函数 + 上下文引用 的组合
总结:形成闭包要有两个条件,缺一不可。 (1)函数 (2)访问其他函数内部变量
2.闭包作用 : 变量污染
* 后期闭包出现较多的一般都会在回调函数里面
*/
//获取元素
let input = document.querySelector('input')
let button = document.querySelector('button')
button.onclick = function(){
//有一个变量存储搜索结果
let str = '6666'
//模拟网络请求:2秒钟之后获取搜索结果
setTimeout(function(){
alert(`搜索结束,本次搜索结果是${str}`)
},2000)
}
</script>
5.总结梳理:
5.1this三种指向
- 全局函数 :函数名( ) ------------------>this指向window
- 对象方法 :对象名.方法名( ) -------> this指向对象
- 构造函数 :new 函数名( )------------>this指向new创建的空对象
5.2call、apply、bind区别
- 相同点:都是修改函数this指向
- 不同点
- 传参方式不同: call用于单个参数,apply用于多个参数
- 执行机制不同: call与apply会立即执行, bind不会立即执行
- call、apply用一次修改一次
- bind;一次修改,终生有效
5.3闭包
- 什么是闭包:以下两种回答都可以
- 闭包是一个访问其他函数内部变量的函数
- 闭包是 函数 + 上下文代码组合
- 闭包作用:解决变量污染
5.4递归
- 什么是递归:函数内部调用自己
- 递归场景
- 深拷贝
- 遍历dom树