JS中this的五种情况
事件绑定
给元素某个事件行为绑定方法,当方法执行this就是操作元素本身
const btn = document.getElementById('btn');
btn.onclick = function(){
console.log(this) // this->btn
}
函数执行(包括自执行函数 & 回调函数)
- 看函数执行前面有没有点,有点前面是谁this就是谁,没有点就是window或者是undefined。
- 自执行函数和回调函数this一般是window,除非函数内部做过特殊处理
const fn = function fn(x,y){
console.log(this,x,y);
}
let obj = {
fn,
name:'obj'
}
fn(10,20); // this->window x->10 y->20
obj.fn(10,20); //this->obj x->10 y->20
new构造函数
new构造函数执行,构造函数体当中的this就是当前创建类的实例。this.xxx=xxx就是给实例加的私有属性
箭头函数
箭头函数中没有自己的this,它所用到的this都是它所处上下文中的this。箭头函数中的this无法更改
call/apply/bind
可以强制改变函数中的this指向
Function.prototype 有以下三个方法
+ call
+ apply
+ bind
所有的函数都是Function类的实例,所以所有函数都可以调用这三个方法;而这三个方法都是用来改变函数中的this指向的
call
- fn基于__proto__找到Function.prototype.call,把call方法执行;
- 在call方法执行的时候:
- context:obj 要改变的this指向 params:[10,20] 执行函数传递的实参信息 this:fn 要执行的函数
- 它干的事情是:立即把fn(this)执行,并且让fn(this)中的this指向obj(context),把10/20(params)传递给fn(this),接受fn(this)执行的返回结果,作为最后的结果返回
const fn = function fn(x,y){
console.log(this,x,y);
}
let obj = {
name:'obj'
}
fn.call(obj,10,20); // this->obj x->10 y->20
fn.call(10,20); //this->new Number(10) x->20 y->undefined (this存储的值:null/undefined/对象)
fn.call(); //this->window/undefined(严格模式)
fn.call(null); //this->window/null(严格模式)
重写call方法
思路:给context设置一个属性(例如:xxx 新增的属性不要和原始context中的属性冲突(设置symbol唯一值属性)),让属性值等于要执行的函数(即:this(fn)),后面就可以基于 context.xxx() 执行,这样既把函数执行了,也让函数中的this改为context
Function.prototype.call = function call(context,...params){
// this->fn context->obj params->[10,20]
if(context == null) context = window;
if(!/^(object|function)$/.test(typeof context)) context = Object(context);
let self = this,
key = Symbol('KEY'),
result;
context[key] = self;
result = context[key](...params);
// delete context[key]; //新增的属性用完记得移除
Reflect.deleteProperty(context,key); //ES6中,可以基于Reflect.deleteProperty移除对象中的属性
return result;
};
call & apply 区别
- 都是把函数立即执行,改变函数中的this指向的(第一个参数是谁,就把this改为谁)
- 唯一区别:apply要求把传递给函数的实参,以数组的形式管理起来(最终效果和call一样,也就是把数组中的每一项,一个个的传递给函数)
- 真实项目中建议大家使用call,因为其性能好一些(做过测试:三个及以上参数,call的性能明显比apply好一些)
const fn = function fn(x,y){
console.log(this,x,y);
}
let obj = {
name:'obj'
}
fn.call(obj,10,20); // this->obj x->10 y->20
fn.apply(obj,[10,20]); //this->obj x->10 y->20
apply获取数组最大值
let arr = [10,26,85,23,69,41];
console.log(Math.max(arr)); // NaN
console.log(Math.max(10,26,85,23,69,41)); //32 Math.max要求一项项的传递数字,不能直接传递数组
console.log(Math.max(...arr)); //32 ES6中的展开运算符
console.log(Math.max.apply(Math,arr)); //32
call VS bind
- call是把函数立即执行,而bind只是预处理函数中的this和参数,函数此时并没有执行
const submit = document.querySelector('#submit');
const boj = { name:'obj'};
const fn = function fn(x,y,ev){
console.log(this,x,y,ev);
};
submit.onclick = fn; //点击按钮 this->submit x->PointerEvent y->undefined
// 需求:点击按钮,fn方法执行,我们想让其中的this变为obj,ev事件对象也存在,再传递10/20
submit.onclick = fn.call(obj,10,20); //这样处理是错误的,因为call是把函数立即执行,还没点击,fn就执行了
submit.onclick = function(ev){
// 先给事件行为绑定匿名函数,当点击的时候,先执行匿名函数(获取到ev事件对象了);在匿名函数执行的时候(this->submit),我们再让真正要处理的fn函数执行,此时就可以基于call去改变this了!
fn.call(obj,10,20,ev);
};
submit.onclick = fn.bind(obj,10,20); //利用bind处理就可以了
重写bind方法
Function.prototype.bind = function bind(context,...params){
//this->fn context->obj params->[10,20]
let self = this;
return function proxy(...args){ //闭包作用域 柯理化函数思想
//args->[ev] this->submit
params = params.concat(args);
return self.call(context,...params);
};
};