如何区分this是谁
首先要重点明确,this执行环境决定了this是谁,而不是this定义环境
- 在全局上下文中执行的this:浏览器是Window,node下则是global。
- 块级私有上下文 & 箭头函数中执行的this:都是继承其上级上下文中的。
- DOM元素事件绑定:给当前元素的某个事件行为绑定方法,当事件行为触发,方法执行,方法中的this是当前DOM元素。
-
- 一般情况下,自执行函数 & 回调函数执行,方法中的this是window【严格模式下undefined】。
- 特殊情况:forEach等数组迭代方法,传递第二个参数,回调函数的this指向第二个参数。【forEach等内部处理的】
- 箭头函数触发第2条规则。
-
- 普通函数执行,看方法名前面是否有“点”,有“点”,那么“点”前面是谁this就是谁。
- 没有“点”,非严格模式下是window,严格模式下是undefined。
- 构造函数执行【new创造的】,构造函数体中的this指向当前类的实例。
只要我们根据上诉总结的6点,this的真真假假再也不是问题~
call & apply 的使用
我们知道this是可以通过自己的操作去改变的,那么我们来了解下什么是call和apply吧~
call和apply可改变函数中的this指向
function fn() {
console.log(this) // window或者undefined
}
fn.call()
fn.apply()
----------------------------
const obj = {}
function fn() {
console.log(this) // obj
}
fn.call(obj, 10, 20)
fn.apply(obj, [10, 20])
不传参数的话,this为window,【严格模式下为undefined】,否则为第一个参数。
call和apply的区别
const obj = {}
function fn(a, b) {
console.log(this, a, b) // {} 1 2
}
fn.call(obj, 1, 2)
fn.apply(obj, [1, 2])
除第一位参数,call是以多个参数形式传入,apply则是以数组的形式传入。但函数内接受都是以多参数形式。
另外,call的性能要高于apply。【c++内部还是要把数组展开,底层apply的实现也是调用了call方法的。】
手写call方法
既然我们了解了call和apply是可以改变this的,想不想再了解下底层的实现逻辑是什么呢?一起来慢慢窥探下吧~
根据我们上面总结的6点知道,this是无法脱离那些基本的机制。所以call的实现其实也并没有加入新的知识点。只是通过一个小技巧,依旧利用上面提到的机制来实现this改变的,所以不要担心哦~
敲黑板!!!:手写call的核心,就是把函数作为传入对象的属性执行,通过上文提到的“点”前面是谁this就是谁的机制,来使得函数的this变成了传入的对象
代码里每一步都有详细的讲解哦~ 不用担心看不懂^^
Function.prototype.call = function(content, ...args) {
if (content == null) content = window // call方法第一个参数不传或者为null和undefined【可以看文章上面不传的情况】
if(!/^(object|function)$/.test(typeof content)) content = Object(content) // 如果传入的是原始类型,那么我们要转化为对象
let key = Symbol('KEY'), // 防止原对象中有相同的key,造成冲突
result
content[key] = this // 这里的this是sum函数,我们把这个函数作为传入content的一个属性
result = content[key](...args) // 这里函数执行时是作为content的属性,所以巧妙的通过“点”修改了this
delete content[key] // 不对原对象进行修改
return result
}
function count(num) {
return this.sum + num
}
const obj = {
sum: 0
}
console.log(count.call(obj, 1)) // 1
apply只是参数不一样,所以只要在参数部分修改即可。
另外提一下bind的实现。因为bind不是立即执行,所以我们用【柯里化思想】返回call函数即可。
Function.prototype.bind = function(content, ...args) {
return () => this.call(content, ...args)
}
console.log(count.bind(obj, 1)()) // 1