this 知识
函数的三种调用方式: 普通函数 对象方法 构造函数
普通函数this的指向问题: 谁调用我, 我就指向谁.
- 普通函数; 函数名() this指向window
- 对象方法: 对象名.方法名() this指向对象
- 构造函数; new 函数名() this指向new创建实例对象
箭头函数this的指向问题: 箭头函数没有this.
在箭头函数里使用this, 本质是通过作用域链找上一级作用域的this.
箭头函数没有this对箭头函数有一些影响:
(1)箭头函数不能作为构造函数
(2)箭头函数不能修改this
(3)事件处理函数一般不用箭头函数
上下文(context): 表示函数内部this的值。
默认情况下, 函数内部的this不能主动修改, 如果需要修改, 则需要修改上下文.
修改上下文的几种方式:call() ,.apply() ,.bind()
1. 函数名.call(this修改后的指向,参数1,参数2......)
应用场景: 万能数据类型检测语法: Object.prototype.toString.call( 数据 )
typeof数据 : 有两种数据类型无法检测, null和数组无法检测,结果都是 'object'- 解决方案: 万能数据类型检测 Object.prototype.toString.call( 数据 ) 万能数据类型检测原理:
- Object.prototype.toString() 内部会返回this的数据类型, 得到固定格式字符串 '[object 数据类型]'
- 使用Object原型中的toString()要想得到数据类型,只需要把this修改成你想要检测的对象
举例:
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]'
2. 函数名.apply(this修改之后的指向, 伪数组或者数组)
apply会自动遍历数组,然后按照顺序逐一传参
应用场景(1): 伪数组转真数组
// 类似于伪数组 本质是 : 对象
let obj = {
0: 20,
1: 66,
2: 87,
3: 90,
length: 4
}
console.log(obj);
//伪数组转真数组
let arr = []
console.log(arr);
// arr.push( obj[0],obj[1],obj[2],obj[3])
//借助 apply自动遍历数组/伪数组 逐一传参特点
//这里不需要修改this,只是借助apply传参的特点. this指向原来是谁,还是设置谁
arr.push.apply(arr, obj)
console.log(arr) // [20, 66, 87, 90]
以前学的伪数组转真数组语法:Array.from( 伪数组 )
应用场景(2): 求数组最大值
//求数组最大值
let arr = [20, 50, 66, 100, 30]
//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)
3. 函数名.bind(this修改后的指向, arg1,arg2......)
bind()语法并不会立即执行函数,而是返回一个修改this指向后的新函数,常用于回调函数
应用场景: 定时器中的this : 默认指向window, 修改定时器中的this,
setTimeout(function(){
console.log(this);
}.bind({name:'111'}),2000)
这里不能使用call() 和apply(), 因为她俩会立即执行函数, 定时器失效啦
闭包
一个函数内使用了外部的变量,那么这个函数和被使用的外部变量一起被称为闭包结构, 通常会再使用一个函数包裹住闭包结构, 以起到解决变量污染问题,让变量被函数保护起来.
示例代码:
const count = 0
setInterval(function () {
console.log(count++)
}, 1000)
上述代码中的count 是一个使用频率很高的变量名,为了避免和其他位置的代码冲突, 一般外面再套一个函数, 可起到保护作用.
function fn() {
const count = 0 // 私有化数据
setInterval(function () {
console.log(count++)
}, 1000)
}
setInterval 第一个参数的匿名函数与 count 构成了闭包。
使用闭包的优缺点:
优点: 私有化数据,在私有化数据的基础上保持数据.
缺点: 可能会导致内存泄漏,内部的变量不会被自动回收掉.
递归
递归函数: 一个函数自己调用自己.
递归函数特点:
- 要有结束条件, 否则会导致死循环
- 能用递归函数实现的需求, 就一定可以用循环调用函数来解决, 只是代码简洁与性能不同而已.
递归的应用场景: 使用递归进行深拷贝
let obj = {
name: '陈平安',
age: 20,
gender: '男',
hobby: ['吃饭', '睡觉', '学习'],
student: {
name: "裴钱",
age: 6
}
}
//console.log(obj['hobby']);
// //使用递归函数
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.age = 17
newObj.gender = '女'
newObj.hobby[0] = '喜欢平安'
newObj.student.name = '小米粒'
newObj.student.age = 7
console.log(obj, newObj)
现在基本上采用json方式 : 实现深拷贝.
语法:let newObj = JSON.parse( JSON.stringify( obj ) )