JS中THIS的五种情况梳理
事件绑定
函数执行(包括自执行函数)
new构造函数
箭头函数
call/apply/bind
1、匿名函数里面的this在非严格模式下是window,在严格模式下是undefined
let obj = {
x: 0,
fn() {
setTimeout(function () {
// this -> window
this.x++;
console.log(obj.x);
}, 1000);
}
};
obj.fn();
2、我们把this存在self里面
let obj = {
x: 0,
fn() {
// this -> obj
let self = this;
setTimeout(function () {
// this -> window
self.x++;
console.log(obj.x);
}, 1000);
}
};
obj.fn();
3、箭头函数里面的this指的是当前上下文中的this
let obj = {
x: 0,
fn() {
//这里面的this指的是fn这个函数的执行上下文中的this
setTimeout(() => {
this.x++;
console.log(obj.x);
}, 1000);
}
};
obj.fn();
4、window
let obj = {
x: 0,
fn: () => {
// this->window
console.log(this);
}
};
obj.fn();
5、直接执行一个函数,里面的this指的是window
6、将函数放在对象里面,那么让这个函数执行,this指向的是这个对象
7、call执行都做什么
fn首先基于__proto__找到Function.prototype上的call方法,把call方法执行
+ 传递的实参 obj
+ call方法中的this -> fn
call方法执行的作用是:把fn「this」执行,并且让方法fn「this」中的this指向变为第一个传递的实参「obj」
function fn(x, y) {
console.log(this, x, y);
return x + y;
}
let obj = {
name: 'obj'
};
fn.call(obj)
// fn.call(obj, 10, 20); //this->obj x->10 y->20
// fn.call(10, 20); //this->10 x->20 y->undefined
// fn.call(); //->非严格模式下:this->window「传递第一个参数是null/undefined也是window」 严格模式下:this->undefined「传递的第一个参数是谁,this就是谁」
8、apply和call的区别
apply和call只有一个区别:传递给执行函数的实参方式不一样
fn.call([context],params1,params2,...)
fn.apply([context],[params1,params2,...])
最后结果都是把params一项项的传递给fn的
// let arr = [10, 20];
// fn.call(obj, arr); //->this:obj x:arr y:undefined
// fn.apply(obj, arr); //->this:obj x:10 y:20
// fn.call(obj, ...arr); //->fn.call(obj, 10, 20) //->this:obj x:10 y:20
总结:call的性能要比apply好一丢丢「尤其是传递的实参在三个以上」
9、应用一:排序数组 (1)sort
(2)Math.max()传入的是一堆数,不是一个数组
(3)这样可以
console.log(Math.max.apply(Math, arr)); //->23
(4)假设法
// let max = arr[0];
// arr.slice(1).forEach(item => {
// if (item > max) {
// max = item;
// }
// });
// console.log(max);
(5)自己拼字符串
// let str = 'Math.max(' + arr + ')'; //->'Math.max(1,5,6,23,14,15)'
// console.log(eval(str)); //->23
10、应用二:鸭子类型(长得像鸭子,称他为鸭子,最主要的是想要让他具备鸭子的特点)
11、arguments.proto===Object.prototype 类数组对象,不能直接使用数组的方法;类数组的原型指向的是Object.prototype 而普通的数组指向的是Array.prototype
12、把类数组转换为数组的方法
(1) let arr = [...arguments];
(2) let arr = Array.from(arguments);
(3) let arr = Array.prototype.slice.call(arguments, 0) <===> let arr = [].slice.call(arguments) 13、数组求和
(1)先转化为数组,再用数组的方法
function fn() {
var arr = [].slice.call(arguments)
return arr.reduce((total,item)=>total+item)
}
(2)直接借用
function fn() {
return [].reduce.call(arguments,(total,item)=>total+item)
}
(3)改变原型
function fn() {
arguments.__proto__ = Array.prototype;
return arguments.reduce((total, item) => total + item);
}
14、更暴力的办法:直接把你的东西抢过来用
//模拟array的实现
Array.prototype.push = function (val) {
// this -> 数组
// 1.把val放置在数组的末尾
// this[this.length]=val;
// 2.数组长度累加
// this.length++;
// return this.length;
};
arr.push(10);
//类数组调用push
let obj = {
2: 3, //1
3: 4, //2
length: 2, //3 4
push: Array.prototype.push
};
obj.push(1); //-> obj[2]=1 obj.length++
obj.push(2); //-> obj[3]=2 obj.length++
console.log(obj); //=>{2:1,3:2,length:4}
15、bind
call/apply都是立即把函数执行「改变THIS和传递参数」
bind没有把函数立即执行,只是把后期要改变的this及传递的参数预先存储起来「柯理化」
function fn(x, y, ev) {
console.log(this, x, y, ev);
return x + y;
}
let obj = {
name: 'obj'
};
// document.onclick = fn; //->点击文档才执行fn this->document x->MouseEvent事件对象 y->undefined
// document.onclick = fn.call(obj, 10, 20); //->立即执行了fn,我们需要点击的时候才执行
// document.onclick = function (ev) {
// // this->document
// fn.call(obj, 10, 20, ev);
// };
document.onclick = fn.bind(obj, 10, 20);
/*
// 执行bind,fn没有立即执行「预先把fn/obj/10/20都存储起来了」,返回一个新函数
let proxy = fn.bind(obj, 10, 20);
// 执行返回的函数,proxy内部帮助我们把fn执行「this和参数该处理都处理了」
proxy();
*/