01-函数的三种调用方式(this指向)
1.复习函数三种调用方式:普通函数 对象方法 构造函数
- 重点:理解this关键字作用:
谁调用这个函数,this指向谁
<!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>
</head>
<body>
<script>
/*
函数三种执行模式 : 全局函数 、 对象方法 、 构造函数
this : 谁 `调用` 我,我就指向谁
1. 全局函数 : this指向window
2. 对象方法 : this指向对象
3. 构造函数 : this指向new创建的空对象
*/
//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>
</body>
</html>
02-函数调用的上下文模式
了解上下文模式注意点
- a.函数上下文三个方法:
call()
apply()
bind()
,它们定义在Function构造函数的原型中 - b.如果将this指向修改为值类型:(number,boolean,string),则函数会自动帮我们包装成对应的引用类型(基本包装类型)
- 值类型:
'123'
,1
,true
- 基本包装类型:
String('123')
,Number(1)
,Boolean(true)
- 值类型:
1.1-函数执行的上下文模式
作用:可以动态修改函数中的this指向
-
call() apply() bind()
异同点
- 相同之处:都可以修改函数中this指向
- 不同点:传参方式不同
- call()语法:
函数名.call(this修改后的指向,arg1,arg2…………)
- apply()语法:
函数名.apply(this修改之后的指向,伪数组或者数组)
- bind()语法:
函数名.bind(this修改后的指向,arg1,arg2....)
- bind()语法并不会立即执行函数,而是返回一个修改指向后的新函数,常用于回调函数
- bind()语法:
- call()语法:
<!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>
</head>
<body>
<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
fn.call({age:18},100,200);
//b. 函数名.apply(修改的this,[数组或伪数组])
//应用场景: 适用于函数原本形参 >=2
fn.apply({age:88},[20,30]);
//c. 函数名.bind(修改的this,arg1,arg2…………)
//特点:这个函数不会立即执行,而是返回一个修改this之后的新函数
//应用场景 : 事件处理函数,定时器
let newFn = fn.bind({name:'坤坤'});
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>
</body>
</html>
//2.3 bind();
//语法: 函数名.bind(this修改后的指向,arg1,arg2....);
let obj = {
name:"文聪兄"
};
function getSum(a,b){
console.log(this);
console.log(a+b);
}
getSum(10,20);//this指向window
let fn = getSum.bind(obj); //bind()不会执行这个函数,而是会返回一个修改了this的函数.
fn(100,200); //此时这个fn,就相当于是修改了this之后的getSum.
//3 回调函数(例如定时器),一般使用bind来修改this指向
let obj = {
name:"班长"
};
//3.1 定时器的回调函数是一个具名函数
function test1(){
console.log(this);
}
let test2 = test1.bind(obj);
setInterval(test2,2000);
//3.2 定时器的回调函数是一个匿名函数
setInterval(function () {
console.log ( this );
}.bind(obj),2000);
1-2- call()调用
<!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>
</head>
<body>
<script>
/*
1.环境对象 this : 谁'调用'我,我就指向谁
普通函数; 函数名() this指向window
对象方法: 对象名.方法名() this指向对象
构造函数; new 函数名() this指向new创建实例对象
*** 默认情况下,函数内部的this不能主动修改. 如果需要修改,则需要使用上下文方式
2.上下文调用 : 修改函数内部的this
2.1 函数名.call(修改后的this,形参1,形参2…………)
2.2 函数名.apply()
2.3 函数名.bind()
3. 面试必问: call 和 apply 和 bind三者区别
*/
function fn(a,b){
console.log( a + b )
console.log( this )
}
// 函数名.call(修改后的this)
fn.call({name:'张三'},10,20)
</script>
</body>
</html>
1.3-call()场景:万能数据类型检测
<!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>
</head>
<body>
<script>
/*
1. typeof 数据 : 有两种数据类型无法检测
null和数组无法检测,结果都是 'object'
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( typeof str )//'string'
console.log( typeof num )//'number'
console.log( typeof bol )//'boolean'
console.log( typeof und )//'undefined'
console.log( typeof nul )//'object'
console.log( typeof arr )//'object'
console.log( typeof fn )//'function'
console.log( typeof obj )//'object'
/* Object.prototype.toString() 返回固定格式字符串 '[object 数据类型]' */
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]
</script>
</body>
</html>
1.4-apply()
<!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>
</head>
<body>
<script>
/*
1.环境对象 this : 谁'调用'我,我就指向谁
普通函数; 函数名() this指向window
对象方法: 对象名.方法名() this指向对象
构造函数; new 函数名() this指向new创建实例对象
2.上下文调用 : 修改函数内部的this
2.1 函数名.call(修改后的this,形参1,形参2…………)
2.2 函数名.apply(修改后的this, 数组或伪数组 )
2.3 函数名.bind()
3. 面试必问: call 和 apply 和 bind三者区别
*/
function fn(a,b){
console.log( a + b )
console.log( this )
}
// 函数名.call(修改后的this,形参1,形参2…………)
fn.call({name:'张三'},10,20)
// 函数名.apply(修改后的this, 数组或伪数组 )
// apply会自动帮你遍历数组,然后按照顺序逐一传参
fn.apply({name:'李四'}, [30,40] )
</script>
</body>
</html>
1.5-apply()场景: 伪数组转真数组
<!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>
</head>
<body>
<script>
//伪数组 本质是 : 对象
let obj = {
0:20,
1:66,
2:88,
3:90,
length:4
}
//伪数组转真数组
let arr = []
// arr.push( obj[0],obj[1],obj[2],obj[3])
//借助 apply自动遍历数组/伪数组 逐一传参特点
//这里不需要修改this,只是借助apply传参的特点. this指向原来是谁,还是设置谁
arr.push.apply( arr,obj )
console.log(arr)
//伪数组转真数组 : Array.from( 伪数组 )
console.log( Array.from(obj) )
</script>
</body>
</html>
1.6-apply()场景:求数组最大值
<!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>
</head>
<body>
<script>
//求数组最大值
let arr = [20,50,66,100,30]
//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. Math.max()
// let max1 = Math.max(arr[0],arr[1],arr[2],arr[3],arr[4])
//这里使用apply只是借助传参特点,this指向不用修改。还是原来的this
let max1 = Math.max.apply(Math,arr)
console.log( max1 )
//ES6求最大值 ...作用和apply类似,也会自动遍历数组,然后逐一传参
let max2 = Math.max(...arr)
console.log(max2)
</script>
</body>
</html>
1.7-bind()
<!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>
</head>
<body>
<script>
/*
1.环境对象 this : 谁'调用'我,我就指向谁
普通函数; 函数名() this指向window
对象方法: 对象名.方法名() this指向对象
构造函数; new 函数名() this指向new创建实例对象
2.上下文调用 : 修改函数内部的this
2.1 函数名.call(修改后的this,形参1,形参2…………)
2.2 函数名.apply(修改后的this, 数组或伪数组 )
2.3 函数名.bind(修改后的this)
* 不会立即执行函数,而是得到一个修改this之后的新函数。
* bind一般用于修改: 定时器函数、事件处理函数
3. 面试必问: call 和 apply 和 bind三者区别
*/
function fn(a,b){
console.log( a + b )
console.log( this )
}
// 函数名.call(修改后的this,形参1,形参2…………)
fn.call({name:'张三'},10,20)
// 函数名.apply(修改后的this, 数组或伪数组 )
// apply会自动帮你遍历数组,然后按照顺序逐一传参
fn.apply({name:'李四'}, [30,40] )
//函数名.bind(修改后的this)
// bind不会立即执行函数,而是返回一个修改this之后的新函数
let newFn = fn.bind({name:'王五'})
newFn(100,200)
newFn(10,20)
</script>
</body>
</html>
1.8-bind()场景:修改定时器中的this指向
<!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>
</head>
<body>
<script>
/*
定时器中的this : 默认指向window
*/
setTimeout(function() {
console.log(this)
}.bind({ name: "1111" }),2000)
// let obj = {
// name: "张三",
// hobby: ["学习", "干饭", "睡觉"],
// sayHi: function() {
// setTimeout(function() {
// console.log(this)
// }, 1000)
// }
// }
// obj.sayHi()
</script>
</body>
</html>
1.9-面试必问:call、apply、bind区别
<!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>
</head>
<body>
<script>
/*
1.环境对象 this : 谁'调用'我,我就指向谁
普通函数; 函数名() this指向window
对象方法: 对象名.方法名() this指向对象
构造函数; new 函数名() this指向new创建实例对象
2.上下文调用 : 修改函数内部的this
2.1 函数名.call(修改后的this,形参1,形参2…………)
2.2 函数名.apply(修改后的this, 数组或伪数组 )
2.3 函数名.bind(修改后的this)
* 不会立即执行函数,而是得到一个修改this之后的新函数。
* bind一般用于修改: 定时器函数、事件处理函数
3.面试必问: call 和 apply 和 bind 三者区别
相同点 : 作用一致,修改函数this指向
不同点 :
传参方式不同 : call是按照顺序传参, apply是数组/伪数组传参
执行机制不同 : call和apply会立即执行函数,而bind不会立即执行而是得到修改this的新函数
*/
</script>
</body>
</html>
03-闭包(closure)
- 传送门:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
一. 闭包的定义
- 1.闭包 : 是一个可以访问其他函数作用域的函数
- 闭包 = 函数 + 上下文的引用,闭包不等于函数。以下代码就构成了闭包:
function fn(){
let a = 1
function fn1() {
console.log(a)
}
fn1()
}
二. 闭包的作用
直接作用:解决变量污染问题,让变量被函数保护起来。
示例代码如下:
let count = 0
setInterval(function () {
console.log(count++)
}, 1000)
以上代码中的 count
是一个使用频率很高的变量名,为了避免和其他位置的代码冲突,可以再使用一个函数把以上代码包裹起来,起到保护作用。
function fn() {
let count = 0
setInterval(function () {
console.log(count++)
}, 1000)
}
以上代码中,setInterval
第一个参数的匿名函数与 count
构成了闭包。
将以上代码改写如下:
function fn() {
let count = 0
function add() {
console.log(count++)
}
setInterval(add, 1000)
}
以上代码中,add + count
构成了闭包。
三. 闭包的案例
-
案例需求:在输入框输入搜索文字,点击百度一下按钮,用定时器模拟网络请求,1 秒之后显示搜索结果;
-
页面结构如下
<div class="box"> <input type="search" name="" id=""> <button>百度一下</button> </div>
-
代码如下:
// 1. 获取元素 let search = document.querySelector('.box input') let btn = document.querySelector('.box button') // 2. 添加点击事件 btn.onclick = function () { // 获取搜索的文字 let text = search.value // 模拟发送网络请求 setTimeout(function () { alert(`您搜索的内容是 ${text} 共搜索到 12345 条结果`) }, 1000) }
闭包小结
- 闭包 = 函数 + 上下文的引用
- 闭包的作用:解决变量污染问题,让变量被函数保护起来。
- 在 ES5 时代,闭包可以解决一些其他 JavaScript 的小 BUG,但随着 ES6
let
等新语法的诞生,之前一些闭包的使用场景已经不再需要,后续在就业课中会讲,帮助大家突击面试。
- 在 ES5 时代,闭包可以解决一些其他 JavaScript 的小 BUG,但随着 ES6
04-递归
1.1-递归函数介绍
本小节知识点
- 1.递归函数:一个函数自己调用自己
- 2.递归函数特点
- a.一定要有结束条件,否则会导致死循环
- b.能用递归函数实现的需求,就一定可以用循环调用函数来解决,只是代码简洁与性能不同而已
<!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>
</head>
<body>
<script>
/*
1. 递归函数 : 在函数中自己调用自己
2. 递归特点
a. 能用递归实现的功能一定可以用循环,只是语法不同
b. 递归一定要有结束的条件,否则会导致死循环
*/
//一个函数递归
// 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();
</script>
</body>
</html>
1.2-递归应用场景:浅拷贝与深拷贝
<!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>
</head>
<body>
<script>
/*浅拷贝与深拷贝概念主要针对于对象这种数据类型
1.浅拷贝: 拷贝的是地址
* 特点:修改拷贝后的数据,原数据也会随之修改
2.深拷贝:拷贝的是数据
* 特点:修改拷贝后的数据,对原数据没有影响
*/
let obj = {
name: 'ikun',
age: 32,
hobby: ['讲课', '敲代码', '黑马程序员'],
class: {
name: '武汉大前端',
salary: [18888, 12000, 10000]
}
}
//1.浅拷贝: 拷贝的是地址
let obj1 = obj
obj1.name = '黑马李宗盛'
console.log(obj, obj1)
//2.深拷贝:拷贝的是数据
//核心原理:使用递归。 只要遇到属性值是引用类型,则遍历。
function kaobei (newObj, obj) {
// 遍历
for (let key in obj) {
if (obj[key] instanceof Array) {
// obj[key] 是数组
// obj[key]是数组
newObj[key] = []
kaobei(newObj[key], obj[key])
} else if (obj[key] instanceof Object) {
// obj[key] 是对象
// obj[key]再遍历拷贝
newObj[key] = {}
kaobei(newObj[key], obj[key])
} else {
newObj[key] = obj[key]
}
}
}
let obj2 = {}
//深拷贝
kaobei(obj2, obj)
//修改拷贝后的数据
obj2.name = '黑马颜值担当'
obj2.hobby = '唱歌'
console.log(obj,obj2)
</script>
</body>
</html>
1.3-递归应用场景:遍历dom树
- 在我们后期vue项目中,有一个这样的案例:服务器返回一个不确定的数据结构。 是一个
多级菜单
,这个数据是不确定的。我们需要根据服务器返回的数据,来生成对应的页面结构
<!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>
1.4-递归小节
- 1.什么是递归函数: 在函数内部调用自己
- 2.递归函数应用:
- 深拷贝与浅拷贝
- 遍历dom树
今天学习重点梳理(高频面试题)
this三种指向
this : 谁 调用
我,我就指向谁
1.全局函数 : this指向window
2.对象方法 : this指向对象
3.构造函数 : this指向new创建的空对象
call、apply、bind区别
- 相同点:都是修改函数this指向
- 不同点
- 传参方式不同 : call是单个传参,apply是数组/伪数组传参
- 执行机制不同 : call和apply会立即执行, bind不会立即执行而是得到修改this的新函数
- call、apply用一次修改一次
- bind;一次修改,终生有效
闭包
- 什么是闭包:以下两种回答都可以
- 闭包是一个访问其他函数内部变量的函数
- 闭包是 函数 + 上下文引用组合
- 闭包作用:解决变量污染
递归
- 什么是递归:函数内部调用自己
- 递归场景
- 深拷贝
- 遍历dom树